fix: cloundTower代码迁移
parent
3bbe3506e3
commit
1bf3dd6db8
src
common/filters
services
platform
services
views
cosa
bills/list
cost
CostOptimizationAllocation
CostOptimizationReport
components
personal/apply/components
resource-apply
service_operate/product/service_catalog
|
@ -39,6 +39,8 @@ export function resourceTypeMonitor(value) {
|
|||
MONITOR_OPENSTACK_HOST: 'OpenStack宿主机',
|
||||
MONITOR_OPENSTACK_VM: 'OpenStack云主机',
|
||||
MONITOR_OPENSTACK_SERVICE: 'OpenStack服务',
|
||||
MONITOR_CLOUDTOWER_HOST: 'CloudTower宿主机',
|
||||
MONITOR_CLOUDTOWER_VM: 'CloudTower云主机',
|
||||
MONITOR_FUSIONCLOUD_HOST: 'FUSIONCLOUD宿主机',
|
||||
MONITOR_FUSIONCLOUD_VM: 'FUSIONCLOUD云主机',
|
||||
MONITOR_MANAGEONE_HOST: 'ManageOne宿主机',
|
||||
|
|
|
@ -179,6 +179,7 @@ export function categoryFilter(value) {
|
|||
ServiceSkuRevoke: '服务产品批量下架',
|
||||
ServiceSkuBatchRelease: '服务产品上架',
|
||||
ServiceSkuBatchRevoke: '服务产品批量下架',
|
||||
EFCApplicationOperate: '服务使用申请',
|
||||
ApplicationOperate: '服务使用申请',
|
||||
AlterationOperate: '服务实例变更',
|
||||
ExtensionOperate: '服务实例延期',
|
||||
|
|
|
@ -1835,7 +1835,9 @@ export function syncVm() {
|
|||
export function modifyVm(params) {
|
||||
return request.put(`${vmUrl}/${params.id}`, wrapperParams(params))
|
||||
}
|
||||
|
||||
export function modifyCloudTowerNetcard(params) {
|
||||
return request.post(`${vmUrl}/updateVmNics`, params)
|
||||
}
|
||||
export function removeVm(id) {
|
||||
const params = { id: id }
|
||||
return request.delete(`${vmUrl}/${id}`, params)
|
||||
|
|
|
@ -51,3 +51,10 @@ export function unsubscribeService(params) {
|
|||
export function delayService(params) {
|
||||
return request.post('/cos/v1/resource/extension', wrapperParams(params))
|
||||
}
|
||||
export function modifyResourceEFC(params) {
|
||||
return request.post('/cos/v1/resource/efc/alteration', wrapperParams({ workOrderTypeCode: 'CloudServerAlteration', ...params }))
|
||||
}
|
||||
// 服务退订
|
||||
export function unsubscribeServiceEFC(params) {
|
||||
return request.post('/cos/v1/resource/efc/cancelation', wrapperParams({ workOrderTypeCode: 'CloudServerCancelation', ...params }))
|
||||
}
|
||||
|
|
|
@ -266,7 +266,8 @@ export default {
|
|||
conditionCloudVendor({
|
||||
condition: JSON.stringify({
|
||||
condition: 'listByTypes',
|
||||
types: ['HUAWEI', 'ALIYUN']
|
||||
// types: ['HUAWEI', 'ALIYUN']
|
||||
types: ['OPENSTACK', 'VMWARE', 'CNWARE', 'EASYSTACK', 'ARCHEROS', 'CLOUDTOWER']
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
|
|
|
@ -514,7 +514,7 @@ export default {
|
|||
conditionCloudVendor({
|
||||
condition: JSON.stringify({
|
||||
condition: 'listByTypes',
|
||||
types: ['OPENSTACK', 'VMWARE', 'MANAGEONE']
|
||||
types: ['OPENSTACK', 'VMWARE', 'INSPURRAIL', 'CNWARE', 'EASYSTACK', 'ARCHEROS', 'CLOUDTOWER', 'FUSIONSPHERE']
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
|
|
|
@ -509,7 +509,7 @@ export default {
|
|||
conditionCloudVendor({
|
||||
condition: JSON.stringify({
|
||||
condition: 'listByTypes',
|
||||
types: ['OPENSTACK', 'VMWARE', 'MANAGEONE', 'TIANYI']
|
||||
types: ['OPENSTACK', 'VMWARE', 'INSPURRAIL', 'CECSTACK', 'CNWARE', 'EASYSTACK', 'ARCHEROS', 'CLOUDTOWER', 'FUSIONSPHERE']
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
|
|
|
@ -505,7 +505,7 @@ export default {
|
|||
conditionCloudVendor({
|
||||
condition: JSON.stringify({
|
||||
condition: 'listByTypes',
|
||||
types: ['OPENSTACK', 'VMWARE', 'MANAGEONE', 'TIANYI']
|
||||
types: ['OPENSTACK', 'VMWARE', 'INSPURRAIL', 'CECSTACK', 'CNWARE', 'EASYSTACK', 'ARCHEROS', 'CLOUDTOWER', 'FUSIONSPHERE']
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<template>
|
||||
<el-dialog title="新增白名单对象" :close-on-click-modal="false" :visible.sync="dialog.visible" width="1200px" top="5vh">
|
||||
<AdvanceTable :cardBorder="false" :searchConfigs="searchConfigs" title="" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total" @selection-change="selectChange">
|
||||
<template #ip="val, record">
|
||||
<div v-if="record.vendorType == 'OPENSTACK' || record.vendorType == 'EASYSTACK' || record.vendorType == 'MANAGEONE' || record.vendorType == 'FUSIONCLOUD'">
|
||||
<span v-for="item in record.privateIpsList" :key="item.networkId">
|
||||
<div v-for="(items1, index) in item.addresses" :key="index">(内网){{ items1.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VMWARE' || record.vendorType == 'INSPURRAIL' || record.vendorType == 'CNWARE' || record.vendorType == 'ZSTACK' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">(内网){{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'HUAWEI' || record.vendorType == 'HCSO'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">{{ item.type == 'floating' ? '(公网)' : '(内网)' }}{{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'QCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.address }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'AZURE' || record.vendorType == 'AWS'">
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'JDCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VOLCENGINE'">
|
||||
<!-- <div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.primaryIpAddress }}</div> -->
|
||||
<div v-if="JSON.parse(record.privateIps)[0].primaryIpAddress">{{ JSON.parse(record.privateIps)[0].primaryIpAddress }}(内网)</div>
|
||||
<div v-if="JSON.parse(record.privateIps)[0].eipAddress">{{ JSON.parse(record.privateIps)[0].eipAddress }}(公网)</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SMARTX' || record.vendorType == 'CLOUDTOWER'">
|
||||
<div>{{ record.managerIp ? '(内网)' + record.managerIp : '--' }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SANGFOR'">
|
||||
<div>{{ record.privateIps }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-if="record.floatingIp">(公网){{ record.floatingIp }}</div>
|
||||
<div v-if="record.vendorType == 'CNWARE' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="(item, index) in record.publicIps" :key="index">(公网){{ item.address }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #spec="val, record"> {{ (record.cpu ? record.cpu : 0) + 'C/' + (record.memory ? record.memory : 0) + 'GB/' }}{{ record.disk == null ? 0 : record.disk + 'GB' }} </template>
|
||||
<template #status="status">
|
||||
<status-icon :type="status | vmStatusColor">{{ status | openstackServer }}</status-icon>
|
||||
</template>
|
||||
<template #memory="val"> {{ val }} GB </template>
|
||||
</AdvanceTable>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="ghost" @click.native="dialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="handleSubmit" :loading="loading">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getVm, getCloudVendor } from 'services/platform/index'
|
||||
import { getProject } from 'services/system/project'
|
||||
import { addWhiteList } from '@/services/soa/costAnalysis'
|
||||
export default {
|
||||
name: 'Dispose',
|
||||
props: {
|
||||
dialog: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
visible: false,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
label: '资源名称',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
label: 'IP地址',
|
||||
prop: 'privateIps',
|
||||
sortable: 'custom',
|
||||
scopedSlots: { customRender: 'ip' }
|
||||
},
|
||||
{
|
||||
label: '规格',
|
||||
prop: 'spec',
|
||||
scopedSlots: { customRender: 'spec' }
|
||||
},
|
||||
{
|
||||
label: '存储总容量',
|
||||
prop: 'disk'
|
||||
},
|
||||
{
|
||||
label: '运行状态',
|
||||
prop: 'status',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
label: '所属平台',
|
||||
prop: 'vendorName'
|
||||
},
|
||||
{
|
||||
label: '所属项目',
|
||||
prop: 'projectName'
|
||||
},
|
||||
{
|
||||
label: '所属用户',
|
||||
prop: 'ownerName'
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'gmtCreate'
|
||||
}
|
||||
],
|
||||
searchConfigs: [
|
||||
{ label: '资源名称', value: 'name', type: 'Input' },
|
||||
{ label: 'IP', value: 'privateIps', type: 'Input' },
|
||||
{
|
||||
label: '状态',
|
||||
value: 'status',
|
||||
type: 'Select',
|
||||
data: [
|
||||
{ id: 'RUNNING', name: '运行中' },
|
||||
{ id: 'BUILDING', name: '创建中' },
|
||||
{ id: 'STOPPED', name: '关机' },
|
||||
{ id: 'SUSPENDED', name: '挂起' },
|
||||
{ id: 'EXCEPTION', name: '异常' },
|
||||
{ id: 'UNKNOWN', name: '断开' }
|
||||
]
|
||||
},
|
||||
{ type: 'Select', label: '所属平台', value: 'vendorId', data: [], service: { api: getCloudVendor, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Select', label: '所属项目', value: 'projectId', data: [], service: { api: getProject, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ value: 'isTemplate', type: 'Const', initValue: false },
|
||||
{ type: 'Const', label: '是否白名单', initValue: 0, value: 'isWhiteList' }
|
||||
],
|
||||
selected: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectChange(rows) {
|
||||
this.selected = rows
|
||||
},
|
||||
getData() {
|
||||
getVm(this.params).then((res) => {
|
||||
this.list = res.data.rows.map((item) => {
|
||||
if (item.privateIps) item.privateIpsList = JSON.parse(item.privateIps)
|
||||
if (item.publicIps) item.publicIps = JSON.parse(item.publicIps)
|
||||
if (item.vendorType === 'SANGFOR' && item.privateIps) {
|
||||
item.privateIps = Object.values(JSON.parse(item.privateIps))
|
||||
.map((item) => Object.keys(item)[0])
|
||||
.toString()
|
||||
}
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.loading = true
|
||||
addWhiteList({ params: this.selected.map((item) => item.id) })
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
this.$emit('success')
|
||||
this.dialog.visible = false
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,237 @@
|
|||
<template>
|
||||
<div>
|
||||
<AdvanceTable :cardBorder="false" :searchConfigs="searchConfigs" title="" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total" @selection-change="selectChange">
|
||||
<template v-slot:action>
|
||||
<el-button type="ghost" @click="handllExport()"> 导出 </el-button>
|
||||
<el-button type="ghost" @click="handleOpenDispose()" :disabled="!selectList.length">处置</el-button>
|
||||
</template>
|
||||
<template #ip="val, record">
|
||||
<div v-if="record.vendorType == 'OPENSTACK' || record.vendorType == 'EASYSTACK' || record.vendorType == 'MANAGEONE' || record.vendorType == 'FUSIONCLOUD'">
|
||||
<span v-for="item in record.privateIpsList" :key="item.networkId">
|
||||
<div v-for="(items1, index) in item.addresses" :key="index">(内网){{ items1.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VMWARE' || record.vendorType == 'INSPURRAIL' || record.vendorType == 'CNWARE' || record.vendorType == 'ZSTACK' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">(内网){{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'HUAWEI' || record.vendorType == 'HCSO'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">{{ item.type == 'floating' ? '(公网)' : '(内网)' }}{{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'QCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.address }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'AZURE' || record.vendorType == 'AWS'">
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'JDCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VOLCENGINE'">
|
||||
<!-- <div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.primaryIpAddress }}</div> -->
|
||||
<div v-if="JSON.parse(record.privateIps)[0].primaryIpAddress">{{ JSON.parse(record.privateIps)[0].primaryIpAddress }}(内网)</div>
|
||||
<div v-if="JSON.parse(record.privateIps)[0].eipAddress">{{ JSON.parse(record.privateIps)[0].eipAddress }}(公网)</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SMARTX' || record.vendorType == 'CLOUDTOWER'">
|
||||
<div>{{ record.managerIp ? '(内网)' + record.managerIp : '--' }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SANGFOR'">
|
||||
<div>{{ record.privateIps }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-if="record.floatingIp">(公网){{ record.floatingIp }}</div>
|
||||
<div v-if="record.vendorType == 'CNWARE' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="(item, index) in record.publicIps" :key="index">(公网){{ item.address }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #status="status">
|
||||
<status-icon :type="status | vmStatusColor">{{ status | openstackServer }}</status-icon>
|
||||
</template>
|
||||
<template #isHandle="val"> {{ val ? '已处置' : '未处置' }} </template>
|
||||
<template #operate="val, record">
|
||||
<el-button type="text" :disabled="record.isHandle" @click="handleOpenDispose(record)">处置</el-button>
|
||||
</template>
|
||||
</AdvanceTable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCloudVendor } from 'services/platform/index'
|
||||
import { getProject } from 'services/system/project'
|
||||
import { downloadChecklist, getChecklist, disposeChecklist } from '@/services/soa/costAnalysis'
|
||||
import dayjs from 'utils/day'
|
||||
export default {
|
||||
name: 'CheckList',
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
type: 'selection',
|
||||
selectable(row) {
|
||||
return !row.isHandle
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '资源名称',
|
||||
width: '140px',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
label: '处置状态',
|
||||
prop: 'isHandle',
|
||||
scopedSlots: { customRender: 'isHandle' }
|
||||
},
|
||||
{
|
||||
label: 'IP地址',
|
||||
prop: 'privateIps',
|
||||
sortable: 'custom',
|
||||
width: '140px',
|
||||
scopedSlots: { customRender: 'ip' }
|
||||
},
|
||||
{
|
||||
label: '规格',
|
||||
width: '100px',
|
||||
prop: 'spec'
|
||||
},
|
||||
{
|
||||
label: '存储总容量(GB)',
|
||||
width: '110px',
|
||||
prop: 'disk'
|
||||
},
|
||||
{
|
||||
label: '运行状态',
|
||||
prop: 'status',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
label: '命中规则',
|
||||
prop: 'ruleName'
|
||||
},
|
||||
{
|
||||
label: 'CPU利用率',
|
||||
width: '90px',
|
||||
prop: 'cpu'
|
||||
},
|
||||
{
|
||||
label: '内存利用率',
|
||||
width: '90px',
|
||||
prop: 'mem'
|
||||
},
|
||||
{
|
||||
label: '总成本消耗(元)',
|
||||
prop: 'totalCost',
|
||||
width: '110px'
|
||||
},
|
||||
{
|
||||
label: '当月成本消耗(元)',
|
||||
prop: 'monthCost',
|
||||
width: '115px'
|
||||
},
|
||||
{
|
||||
label: '季度成本消耗(元)',
|
||||
prop: 'quarterCost',
|
||||
width: '115px'
|
||||
},
|
||||
{
|
||||
label: '当年成本消耗(元)',
|
||||
prop: 'yearCost',
|
||||
width: '115px'
|
||||
},
|
||||
{
|
||||
label: '所属平台',
|
||||
width: '120px',
|
||||
prop: 'vendorName'
|
||||
},
|
||||
{
|
||||
label: '所属项目',
|
||||
width: '120px',
|
||||
prop: 'projectName'
|
||||
},
|
||||
{
|
||||
label: '所属用户',
|
||||
width: '110px',
|
||||
prop: 'ownerName'
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
width: '160px',
|
||||
prop: 'gmtCreate'
|
||||
},
|
||||
{
|
||||
label: '运行时长',
|
||||
width: '140px',
|
||||
prop: 'duration'
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
width: '80px',
|
||||
scopedSlots: { customRender: 'operate' },
|
||||
fixed: 'right'
|
||||
}
|
||||
],
|
||||
searchConfigs: [
|
||||
{ label: '资源名称', value: 'name', type: 'Input' },
|
||||
{ label: 'IP', value: 'ip', type: 'Input' },
|
||||
{ type: 'Select', label: '所属平台', value: 'vendorId', data: [], service: { api: getCloudVendor, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Select', label: '所属项目', value: 'projectId', data: [], service: { api: getProject, params: { simple: true }, attr: 'data.rows' } }
|
||||
],
|
||||
selectList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getParams() {
|
||||
const { params, page, rows } = this.params
|
||||
const newParams = { page, rows }
|
||||
JSON.parse(params || '[]').map((item) => {
|
||||
Object.keys(item.param).map((key) => {
|
||||
newParams[key] = item.param[key]
|
||||
})
|
||||
})
|
||||
return newParams
|
||||
},
|
||||
getData() {
|
||||
getChecklist(this.getParams()).then((res) => {
|
||||
if (res.success) {
|
||||
this.list = res.data.rows.map((item) => {
|
||||
if (item.privateIps) item.privateIpsList = JSON.parse(item.privateIps)
|
||||
if (item.publicIps) item.publicIps = JSON.parse(item.publicIps)
|
||||
if (item.vendorType === 'SANGFOR' && item.privateIps) {
|
||||
item.privateIps = Object.values(JSON.parse(item.privateIps))
|
||||
.map((item) => Object.keys(item)[0])
|
||||
.toString()
|
||||
}
|
||||
// 当前时间 - 创建时间 = 运行时长
|
||||
// xx 天 xx 小时
|
||||
item.duration = item.gmtCreate ? dayjs().diff(dayjs(item.gmtCreate), 'day') + '天' + dayjs().diff(dayjs(item.gmtCreate), 'hour') + '小时' : '虚拟机不存在或已被删除'
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
selectChange(rows) {
|
||||
this.selectList = rows
|
||||
},
|
||||
async handleOpenDispose(record) {
|
||||
const ids = record ? [record.id] : this.selectList.map(({ id }) => id)
|
||||
const res = await disposeChecklist(ids)
|
||||
if (!res.success) return
|
||||
this.$message.success(res.message)
|
||||
this.getData()
|
||||
},
|
||||
handllExport() {
|
||||
const params = this.getParams()
|
||||
delete params.page
|
||||
delete params.rows
|
||||
downloadChecklist(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeName" type="border-card">
|
||||
<el-tab-pane name="CheckList" label="优化清单">
|
||||
<CheckList v-if="activeName == 'CheckList'"></CheckList>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="Rule" label="规则配置">
|
||||
<Rule v-if="activeName == 'Rule'"></Rule>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="Whitelist" label="白名单管理">
|
||||
<Whitelist v-if="activeName == 'Whitelist'"></Whitelist>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CheckList from './checklist.vue'
|
||||
import Rule from './rule.vue'
|
||||
import Whitelist from './whitelist.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CheckList,
|
||||
Rule,
|
||||
Whitelist
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeName: 'CheckList'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,258 @@
|
|||
<template>
|
||||
<div class="wrapper">
|
||||
<AdvanceTable :cardBorder="false" :searchConfigs="searchConfigs" :data="list" :params="params" :columns="columns" :get-list="getList" :total="total">
|
||||
<template v-slot:action>
|
||||
<el-button type="primary" @click="handleCreate()"> 新增 </el-button>
|
||||
</template>
|
||||
<template #rules="val">
|
||||
<span v-for="(item, index) in val" :key="index"> {{ item.metric | alarmMetric }}{{ item.valueType | valueTypeFilter }}{{ item.operator }}{{ item.value }}持续{{ item.time }}分钟<template v-if="index !== val.length - 1">,</template> </span>
|
||||
</template>
|
||||
<template #operate="val, record">
|
||||
<el-button type="text" @click="handleCreate(record)"><i class="el-icon-edit"></i>编辑</el-button>
|
||||
<el-button type="text" @click="handleDelete(record.id)"><i class="el-icon-delete"></i> 删除</el-button>
|
||||
</template>
|
||||
</AdvanceTable>
|
||||
<el-dialog :title="textMap[dialogStatus]" :close-on-click-modal="false" v-if="addFlag" :visible.sync="addFlag" width="1200px">
|
||||
<basic-form :model="addData" ref="addData" label-width="110px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<basic-form-item label="名称:" prop="name" validate="required">
|
||||
<el-input v-model="addData.name" auto-complete="off"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="规则:">
|
||||
<el-row class="rule-box">
|
||||
<el-col :span="24" v-for="(row, index) in addData.rules" :key="index" class="m-t">
|
||||
<el-row :gutter="10">
|
||||
<span class="m-l pull-left" style="display: inline-block; width: 110px">{{ row.metric | alarmMetric }}:</span>
|
||||
<el-col :span="4">
|
||||
<el-select v-model="row.valueType" clearable>
|
||||
<el-option v-for="item in valueType" :key="item.value" :label="item.name" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-select v-model="row.operator" clearable>
|
||||
<el-option v-for="(item, indexItem) in relationList" :key="indexItem" :label="item.value" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-input-number :min="0" v-model="row.value" auto-complete="off" @blur="changeValue(row.value, index)"></el-input-number>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
持续
|
||||
<el-input-number :min="0" :step="1" step-strictly v-model="row.time" auto-complete="off"></el-input-number>
|
||||
分钟
|
||||
</el-col>
|
||||
<el-col :span="4" v-if="index !== addData.rules.length - 1">
|
||||
<el-select v-model="row.relation" clearable>
|
||||
<el-option v-for="item in relationType" :key="item.value" :label="item.name" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="ghost" @click.native="addFlag = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="addSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { getRules, createRules, modifyRules, deleteRule } from '@/services/soa/costAnalysis'
|
||||
const relationList = [{ value: '>' }, { value: '>=' }, { value: '==' }, { value: '<' }, { value: '<=' }, { value: '!=' }]
|
||||
const valueType = [
|
||||
{ name: '最大值', value: 'max' },
|
||||
{ name: '最小值', value: 'min' },
|
||||
{ name: '平均值', value: 'avg' }
|
||||
]
|
||||
const relationType = [
|
||||
{ name: '与', value: 'and' },
|
||||
{ name: '或', value: 'or' }
|
||||
]
|
||||
const defaultRuleItem = { operator: '', value: '', valueType: '', relation: '', time: '', timeunit: 'm' }
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
label: '名称',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
label: '规则明细',
|
||||
prop: 'rules',
|
||||
width: '700px',
|
||||
scopedSlots: { customRender: 'rules' }
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'gmtCreate'
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
prop: 'createName'
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
width: '220px',
|
||||
scopedSlots: { customRender: 'operate' }
|
||||
}
|
||||
],
|
||||
searchConfigs: [{ type: 'Input', label: '名称', value: 'name' }],
|
||||
relationList,
|
||||
relationType,
|
||||
valueType,
|
||||
list: null,
|
||||
total: null,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
// 新增编辑
|
||||
addFlag: false,
|
||||
addData: {},
|
||||
textMap: {
|
||||
update: '编辑优化分析',
|
||||
create: '新增优化分析'
|
||||
},
|
||||
dialogStatus: ''
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
valueTypeFilter(val) {
|
||||
return valueType.find(({ value }) => value === val).name
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
getRules(this.params).then((data) => {
|
||||
if (data.success) {
|
||||
this.list = data.data.rows.map((item) => {
|
||||
item.rules = JSON.parse(item.rules || '[]')
|
||||
return item
|
||||
})
|
||||
this.total = data.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
// 查询
|
||||
handleSearch(params) {
|
||||
this.params.page = 1
|
||||
this.params.params = params
|
||||
this.getList()
|
||||
},
|
||||
handleCreate(data) {
|
||||
if (data) {
|
||||
this.addData = cloneDeep(data)
|
||||
this.dialogStatus = 'update'
|
||||
const type = []
|
||||
this.addData.rules.forEach((item) => {
|
||||
type.push(item.metric)
|
||||
})
|
||||
if (type.indexOf('cpuUsageAverage') == -1) {
|
||||
this.addData.rules.push({ metric: 'memUsageAverage', ...cloneDeep(defaultRuleItem) })
|
||||
}
|
||||
if (type.indexOf('memUsageAverage') == -1) {
|
||||
this.addData.rules.push({ metric: 'memUsageAverage', ...cloneDeep(defaultRuleItem) })
|
||||
}
|
||||
if (type.indexOf('diskUsedPercent') == -1) {
|
||||
this.addData.rules.push({ metric: 'diskUsedPercent', ...cloneDeep(defaultRuleItem) })
|
||||
}
|
||||
this.addFlag = true
|
||||
} else {
|
||||
this.addData = {
|
||||
name: '',
|
||||
rules: [
|
||||
{ metric: 'cpuUsageAverage', ...cloneDeep(defaultRuleItem) },
|
||||
{ metric: 'memUsageAverage', ...cloneDeep(defaultRuleItem) },
|
||||
{ metric: 'diskUsedPercent', ...cloneDeep(defaultRuleItem) }
|
||||
]
|
||||
}
|
||||
this.dialogStatus = 'create'
|
||||
this.addFlag = true
|
||||
}
|
||||
},
|
||||
addSubmit() {
|
||||
this.$refs.addData.validate((valid) => {
|
||||
if (valid) {
|
||||
const rulesFlag = this.addData.rules.every((item) => {
|
||||
if ((item.operator && item.value && item.valueType && item.time) || (!item.operator && !item.value && !item.valueType && !item.time)) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
if (!rulesFlag) {
|
||||
return this.$message({
|
||||
message: '请确保规则信息填写完整',
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
const addData = {
|
||||
rules: []
|
||||
}
|
||||
const edit = ['id', 'name', 'type', 'gmtCreate', 'creatorId', 'deleted']
|
||||
edit.forEach((attr) => {
|
||||
addData[attr] = this.addData[attr]
|
||||
})
|
||||
addData.rules = Object.assign([], this.addData.rules)
|
||||
for (let j = 0; j < addData.rules.length; j++) {
|
||||
if (!addData.rules[j].operator) {
|
||||
addData.rules.splice(j, 1)
|
||||
j--
|
||||
}
|
||||
}
|
||||
if (!addData.rules.length) {
|
||||
return this.$message.error('至少选一项规则填写!')
|
||||
}
|
||||
if (this.dialogStatus == 'update') {
|
||||
modifyRules(addData).then((data) => {
|
||||
if (data.success) {
|
||||
this.$message.success(data.message)
|
||||
this.getList()
|
||||
this.addFlag = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
createRules(addData).then((data) => {
|
||||
if (data.success) {
|
||||
this.$message.success(data.message)
|
||||
this.getList()
|
||||
this.addFlag = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDelete(id) {
|
||||
this.$confirm('您确定要删除该规则吗?', '提示', {
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(() => {
|
||||
deleteRule(id).then((data) => {
|
||||
if (data.success) {
|
||||
this.$message.success(data.message)
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
changeValue(value, index) {
|
||||
if (value < 0) {
|
||||
this.addData.rules[index].value = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<div>
|
||||
<AdvanceTable :cardBorder="false" :searchConfigs="searchConfigs" title="" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total">
|
||||
<template v-slot:action>
|
||||
<el-button type="primary" @click="handllAdd"> 新增 </el-button>
|
||||
</template>
|
||||
|
||||
<template #ip="val, record">
|
||||
<div v-if="record.vendorType == 'OPENSTACK' || record.vendorType == 'EASYSTACK' || record.vendorType == 'MANAGEONE' || record.vendorType == 'FUSIONCLOUD'">
|
||||
<span v-for="item in record.privateIpsList" :key="item.networkId">
|
||||
<div v-for="(items1, index) in item.addresses" :key="index">(内网){{ items1.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VMWARE' || record.vendorType == 'INSPURRAIL' || record.vendorType == 'CNWARE' || record.vendorType == 'ZSTACK' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">(内网){{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'HUAWEI' || record.vendorType == 'HCSO'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">{{ item.type == 'floating' ? '(公网)' : '(内网)' }}{{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'QCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.address }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'AZURE' || record.vendorType == 'AWS'">
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'JDCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VOLCENGINE'">
|
||||
<!-- <div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.primaryIpAddress }}</div> -->
|
||||
<div v-if="JSON.parse(record.privateIps)[0].primaryIpAddress">{{ JSON.parse(record.privateIps)[0].primaryIpAddress }}(内网)</div>
|
||||
<div v-if="JSON.parse(record.privateIps)[0].eipAddress">{{ JSON.parse(record.privateIps)[0].eipAddress }}(公网)</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SMARTX' || record.vendorType == 'CLOUDTOWER'">
|
||||
<div>{{ record.managerIp ? '(内网)' + record.managerIp : '--' }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SANGFOR'">
|
||||
<div>{{ record.privateIps }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-if="record.floatingIp">(公网){{ record.floatingIp }}</div>
|
||||
<div v-if="record.vendorType == 'CNWARE' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="(item, index) in record.publicIps" :key="index">(公网){{ item.address }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #spec="val, record"> {{ (record.cpu ? record.cpu : 0) + 'C/' + (record.memory ? record.memory : 0) + 'GB/' }}{{ record.disk == null ? 0 : record.disk + 'GB' }} </template>
|
||||
<template #status="status">
|
||||
<status-icon :type="status | vmStatusColor">{{ status | openstackServer }}</status-icon>
|
||||
</template>
|
||||
<template #memory="val"> {{ val }} GB </template>
|
||||
<template #operate="val, record">
|
||||
<el-button type="text" @click="handleRemove(record)">移除</el-button>
|
||||
</template>
|
||||
</AdvanceTable>
|
||||
<AddWhitelist v-if="addDialog.visible" :dialog="addDialog" @success="getData"></AddWhitelist>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCloudVendor } from 'services/platform/index'
|
||||
import { getProject } from 'services/system/project'
|
||||
import AddWhitelist from './addWhitelist.vue'
|
||||
import { getWhiteList, removeWhiteList } from '@/services/soa/costAnalysis'
|
||||
import dayjs from 'utils/day'
|
||||
export default {
|
||||
name: 'Whitelist',
|
||||
components: { AddWhitelist },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
label: '资源名称',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
label: 'IP地址',
|
||||
prop: 'privateIps',
|
||||
sortable: 'custom',
|
||||
scopedSlots: { customRender: 'ip' }
|
||||
},
|
||||
{
|
||||
label: '规格',
|
||||
prop: 'spec',
|
||||
scopedSlots: { customRender: 'spec' }
|
||||
},
|
||||
{
|
||||
label: '存储总容量(GB)',
|
||||
prop: 'disk'
|
||||
},
|
||||
{
|
||||
label: '运行状态',
|
||||
prop: 'status',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
{
|
||||
label: '所属平台',
|
||||
prop: 'vendorName'
|
||||
},
|
||||
{
|
||||
label: '所属项目',
|
||||
prop: 'projectName'
|
||||
},
|
||||
{
|
||||
label: '所属用户',
|
||||
prop: 'ownerName'
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'gmtCreate'
|
||||
},
|
||||
{
|
||||
label: '运行时长',
|
||||
prop: 'duration'
|
||||
},
|
||||
{
|
||||
label: '操作',
|
||||
width: '120px',
|
||||
scopedSlots: { customRender: 'operate' }
|
||||
}
|
||||
],
|
||||
searchConfigs: [
|
||||
{ label: '资源名称', value: 'name', type: 'Input' },
|
||||
{ label: 'IP', value: 'privateIps', type: 'Input' },
|
||||
{
|
||||
label: '状态',
|
||||
value: 'status',
|
||||
type: 'Select',
|
||||
data: [
|
||||
{ id: 'RUNNING', name: '运行中' },
|
||||
{ id: 'BUILDING', name: '创建中' },
|
||||
{ id: 'STOPPED', name: '关机' },
|
||||
{ id: 'SUSPENDED', name: '挂起' },
|
||||
{ id: 'EXCEPTION', name: '异常' },
|
||||
{ id: 'UNKNOWN', name: '断开' }
|
||||
]
|
||||
},
|
||||
{ type: 'Select', label: '所属平台', value: 'vendorId', data: [], service: { api: getCloudVendor, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Select', label: '所属项目', value: 'projectId', data: [], service: { api: getProject, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Const', label: '是否白名单', initValue: 1, value: 'isWhiteList' }
|
||||
],
|
||||
addDialog: {
|
||||
visible: false,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
getWhiteList(this.params).then((res) => {
|
||||
this.list = res.data.rows.map((item) => {
|
||||
if (item.privateIps) item.privateIpsList = JSON.parse(item.privateIps)
|
||||
if (item.publicIps) item.publicIps = JSON.parse(item.publicIps)
|
||||
if (item.vendorType === 'SANGFOR' && item.privateIps) {
|
||||
item.privateIps = Object.values(JSON.parse(item.privateIps))
|
||||
.map((item) => Object.keys(item)[0])
|
||||
.toString()
|
||||
}
|
||||
// 当前时间 - 创建时间 = 运行时长
|
||||
// xx 天 xx 小时
|
||||
item.duration = item.gmtCreate ? dayjs().diff(dayjs(item.gmtCreate), 'day') + '天' + dayjs().diff(dayjs(item.gmtCreate), 'hour') + '小时' : '虚拟机不存在或已被删除'
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
})
|
||||
},
|
||||
handleRemove(record) {
|
||||
this.$confirm('确定移除该资源吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
removeWhiteList({ id: record.id }).then((res) => {
|
||||
if (res.success) {
|
||||
this.getData()
|
||||
this.$message.success(res.message)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handllAdd() {
|
||||
this.addDialog = {
|
||||
visible: true,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<AdvanceTable :cardBorder="false" title="" :searchConfigs="searchConfigs" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total">
|
||||
<template #ip="val, record">
|
||||
<div v-if="record.vendorType == 'OPENSTACK' || record.vendorType == 'EASYSTACK' || record.vendorType == 'MANAGEONE' || record.vendorType == 'FUSIONCLOUD'">
|
||||
<span v-for="item in record.privateIpsList" :key="item.networkId">
|
||||
<div v-for="(items1, index) in item.addresses" :key="index">(内网){{ items1.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VMWARE' || record.vendorType == 'INSPURRAIL' || record.vendorType == 'CNWARE' || record.vendorType == 'ZSTACK' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">(内网){{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'HUAWEI' || record.vendorType == 'HCSO'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">{{ item.type == 'floating' ? '(公网)' : '(内网)' }}{{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'QCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.address }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'AZURE' || record.vendorType == 'AWS'">
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'JDCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VOLCENGINE'">
|
||||
<!-- <div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.primaryIpAddress }}</div> -->
|
||||
<div v-if="JSON.parse(record.privateIps)[0].primaryIpAddress">{{ JSON.parse(record.privateIps)[0].primaryIpAddress }}(内网)</div>
|
||||
<div v-if="JSON.parse(record.privateIps)[0].eipAddress">{{ JSON.parse(record.privateIps)[0].eipAddress }}(公网)</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SMARTX' || record.vendorType == 'CLOUDTOWER'">
|
||||
<div>{{ record.managerIp ? '(内网)' + record.managerIp : '--' }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SANGFOR'">
|
||||
<div>{{ record.privateIps }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-if="record.floatingIp">(公网){{ record.floatingIp }}</div>
|
||||
<div v-if="record.vendorType == 'CNWARE' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="(item, index) in record.publicIps" :key="index">(公网){{ item.address }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #optimizeType="val">
|
||||
{{ val | statusList }}
|
||||
</template>
|
||||
<template #beforeSpec="val, record"> {{ record.beforCpu }}C/{{ record.beforMem }}GB </template>
|
||||
<template #afterSpec="val, record"> {{ record.afterCpu }}C/{{ record.afterMem }}GB </template>
|
||||
<template #memory="val"> {{ val }} GB </template>
|
||||
</AdvanceTable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCloudVendor } from 'services/platform/index'
|
||||
import { getCostOptimizationMonth } from '@/services/soa/costAnalysis'
|
||||
import { getProject } from 'services/system/project'
|
||||
import dayjs from 'utils/day'
|
||||
const statusList = [
|
||||
{
|
||||
name: '变更',
|
||||
id: 'change'
|
||||
},
|
||||
{
|
||||
name: '清退',
|
||||
id: 'unsubscribe'
|
||||
}
|
||||
]
|
||||
export default {
|
||||
name: 'CheckList',
|
||||
props: {
|
||||
detail: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
statusList(val) {
|
||||
return statusList.find((item) => item.id === val)?.name || 'val'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
label: '资源名称',
|
||||
prop: 'serverName'
|
||||
},
|
||||
{
|
||||
label: 'IP地址',
|
||||
prop: 'privateIps',
|
||||
sortable: 'custom',
|
||||
scopedSlots: { customRender: 'ip' }
|
||||
},
|
||||
{
|
||||
label: '优化类型',
|
||||
prop: 'optimizeType',
|
||||
scopedSlots: { customRender: 'optimizeType' }
|
||||
},
|
||||
{
|
||||
label: '优化完成时间',
|
||||
prop: 'optimizeTime'
|
||||
},
|
||||
{
|
||||
label: '优化前规格',
|
||||
prop: 'beforeSpec',
|
||||
scopedSlots: { customRender: 'beforeSpec' }
|
||||
},
|
||||
{
|
||||
label: '优化后规格',
|
||||
prop: 'afterSpec',
|
||||
scopedSlots: { customRender: 'afterSpec' }
|
||||
},
|
||||
{
|
||||
label: '优化前存储总容量(GB)',
|
||||
prop: 'beforDisk'
|
||||
},
|
||||
{
|
||||
label: '优化后存储总容量(GB)',
|
||||
prop: 'afterDisk'
|
||||
},
|
||||
{
|
||||
label: '所属平台',
|
||||
prop: 'vendorName'
|
||||
},
|
||||
{
|
||||
label: '所属项目',
|
||||
prop: 'projectName'
|
||||
},
|
||||
{
|
||||
label: '所属用户',
|
||||
prop: 'ownerName'
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createDate'
|
||||
}
|
||||
],
|
||||
searchConfigs: [
|
||||
{ label: '资源名称', value: 'serverName', type: 'Input' },
|
||||
{ label: 'IP', value: 'privateIps', type: 'Input' },
|
||||
{ type: 'Select', label: '所属平台', value: 'vendorId', data: [], service: { api: getCloudVendor, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Select', label: '所属项目', value: 'projectId', data: [], service: { api: getProject, params: { simple: true }, attr: 'data.rows' } },
|
||||
{
|
||||
type: 'Select',
|
||||
label: '优化类型',
|
||||
value: 'optimizeType',
|
||||
data: statusList
|
||||
},
|
||||
{ label: '报表ID', value: 'optimizeTime', type: 'DateRange', initValue: [dayjs(this.detail.month).startOf('month').format('YYYY-MM-DD 00:00:00'), dayjs(this.detail.month).endOf('month').format('YYYY-MM-DD 23:59:59')] }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getData() {
|
||||
getCostOptimizationMonth(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.list = res.data.data.map((item) => {
|
||||
item.beforMem = item.beforMem * 1
|
||||
item.afterMem = item.afterMem * 1
|
||||
if (item.privateIps) item.privateIpsList = JSON.parse(item.privateIps)
|
||||
if (item.publicIps) item.publicIps = JSON.parse(item.publicIps)
|
||||
if (item.vendorType === 'SANGFOR' && item.privateIps) {
|
||||
item.privateIps = Object.values(JSON.parse(item.privateIps))
|
||||
.map((item) => Object.keys(item)[0])
|
||||
.toString()
|
||||
}
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,130 @@
|
|||
<template>
|
||||
<div>
|
||||
<AdvanceTable title="成本优化报表" :isInitSearch="false" :searchConfigs="searchConfigs" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total">
|
||||
<template v-slot:action>
|
||||
<TimeSelect ref="timeSelectRef" @success="getData">
|
||||
<el-button type="primary" @click="handllExport()"> 导出 </el-button>
|
||||
</TimeSelect>
|
||||
</template>
|
||||
<template #month="val, record">
|
||||
<span class="detail-href" @click="getDetail(record)">{{ val }}</span>
|
||||
</template>
|
||||
</AdvanceTable>
|
||||
<common-detail v-if="detailFlag" :title="detail.orderSn" @goBack="detailFlag = false">
|
||||
<template v-slot:item_container>
|
||||
<common-detail-item :label="item.label" v-for="item in columns" :key="item.prop">{{ detail[item.prop] }}</common-detail-item>
|
||||
</template>
|
||||
<el-tabs value="checkList">
|
||||
<el-tab-pane label="优化清单" name="checkList">
|
||||
<CheckList :detail="detail"></CheckList>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</common-detail>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCostOptimization, downloadCostOptimization, getCostOptimizationCount } from '@/services/soa/costAnalysis'
|
||||
import Block from '../components/Block.js'
|
||||
import TimeSelect from '../components/TimeSelect.vue'
|
||||
import CheckList from './CheckList'
|
||||
|
||||
export default {
|
||||
mixins: [Block],
|
||||
components: { TimeSelect, CheckList },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
label: '月份',
|
||||
prop: 'month',
|
||||
scopedSlots: { customRender: 'month' }
|
||||
},
|
||||
{
|
||||
label: 'CPU优化量(核)',
|
||||
prop: 'cpu'
|
||||
},
|
||||
{
|
||||
label: '内存优化量(GB)',
|
||||
prop: 'memory'
|
||||
},
|
||||
{
|
||||
label: '存储优化量(GB)',
|
||||
prop: 'disk'
|
||||
},
|
||||
{
|
||||
label: '变配资源(台)',
|
||||
prop: 'changeCount'
|
||||
},
|
||||
{
|
||||
label: '清退资源(台)',
|
||||
prop: 'unsubscribeCount'
|
||||
}
|
||||
],
|
||||
searchConfigs: [],
|
||||
detail: {},
|
||||
detailFlag: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
getDetail(data) {
|
||||
this.detail = data
|
||||
this.detailFlag = true
|
||||
},
|
||||
getData() {
|
||||
const params = this.$refs.timeSelectRef.params
|
||||
if (params.type !== 'Years' && params.year && !params.month) return this.$message.error('请选择月份')
|
||||
getCostOptimization({ page: this.params.page, rows: this.params.rows, time: JSON.stringify(params.time) }).then((res) => {
|
||||
if (res.success) {
|
||||
this.list = res.data.data
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
getCostOptimizationCount({ page: this.params.page, rows: this.params.rows, time: JSON.stringify(params.time) }).then((res) => {
|
||||
setTimeout(() => {
|
||||
this.GeneratorBlockComponent([
|
||||
{
|
||||
name: '总节省成本',
|
||||
value: res.data?.money ?? '--',
|
||||
unit: '元'
|
||||
},
|
||||
{
|
||||
name: 'CPU优化量',
|
||||
value: res.data?.cpu ?? '--',
|
||||
unit: '核'
|
||||
},
|
||||
{
|
||||
name: '内存优化量',
|
||||
value: res.data?.memory ?? '--',
|
||||
unit: 'GB'
|
||||
},
|
||||
{
|
||||
name: '存储优化量',
|
||||
value: res.data?.disk ?? '--',
|
||||
unit: 'GB'
|
||||
}
|
||||
])
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
|
||||
handllExport() {
|
||||
const params = this.$refs.timeSelectRef.params
|
||||
if (params.type !== 'Years' && params.year && !params.month) return this.$message.error('请选择月份')
|
||||
downloadCostOptimization({ page: this.params.page, rows: this.params.rows, time: JSON.stringify(params.time) })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '../components/Block.scss';
|
||||
</style>
|
|
@ -0,0 +1,248 @@
|
|||
<template>
|
||||
<el-card>
|
||||
<div slot="header">
|
||||
<div>
|
||||
全局资源监测
|
||||
<!-- <span class="pull-right">最后更新时间:{{}}</span> -->
|
||||
</div>
|
||||
<div class="m-t">
|
||||
<el-alert :closable="false">汇总云管平台中全量云资源的真实使用情况以及成本占用数据,结合多种监控指标维度帮助用户有效了解平台运营层面的全局走势,以便及时采集相应优化举措,提升管理质量。</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
<AdvanceTable :cardBorder="false" title="" :searchConfigs="searchConfigs" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total">
|
||||
<template v-slot:action>
|
||||
<el-button type="primary" @click="handllExport()"> 导出 </el-button>
|
||||
<!-- <el-button type="primary" @click="handllRefresh()"> 更新 </el-button> -->
|
||||
</template>
|
||||
|
||||
<template #ip="val, record">
|
||||
<div v-if="record.vendorType == 'OPENSTACK' || record.vendorType == 'EASYSTACK' || record.vendorType == 'MANAGEONE' || record.vendorType == 'FUSIONCLOUD'">
|
||||
<span v-for="item in record.privateIpsList" :key="item.networkId">
|
||||
<div v-for="(items1, index) in item.addresses" :key="index">(内网){{ items1.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VMWARE' || record.vendorType == 'INSPURRAIL' || record.vendorType == 'CNWARE' || record.vendorType == 'ZSTACK' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">(内网){{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'HUAWEI' || record.vendorType == 'HCSO'">
|
||||
<div v-for="item in record.privateIpsList" :key="item.address">{{ item.type == 'floating' ? '(公网)' : '(内网)' }}{{ !item.address ? '' : item.address + (!item.networkName ? '' : '(' + item.networkName + ')') }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'QCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.address }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'AZURE' || record.vendorType == 'AWS'">
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'JDCLOUD'">
|
||||
<div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'VOLCENGINE'">
|
||||
<!-- <div v-for="(item, index) in record.privateIpsList" :key="index">(内网){{ item.primaryIpAddress }}</div> -->
|
||||
<div v-if="JSON.parse(record.privateIps)[0].primaryIpAddress">{{ JSON.parse(record.privateIps)[0].primaryIpAddress }}(内网)</div>
|
||||
<div v-if="JSON.parse(record.privateIps)[0].eipAddress">{{ JSON.parse(record.privateIps)[0].eipAddress }}(公网)</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SMARTX' || record.vendorType == 'CLOUDTOWER'">
|
||||
<div>{{ record.managerIp ? '(内网)' + record.managerIp : '--' }}</div>
|
||||
</div>
|
||||
<div v-else-if="record.vendorType == 'SANGFOR'">
|
||||
<div>{{ record.privateIps }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="item in record.privateIpsList" :key="item">(内网){{ item }}</div>
|
||||
</div>
|
||||
<div v-if="record.floatingIp">(公网){{ record.floatingIp }}</div>
|
||||
<div v-if="record.vendorType == 'CNWARE' || record.vendorType == 'CECSTACK'">
|
||||
<div v-for="(item, index) in record.publicIps" :key="index">(公网){{ item.address }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #spec="val, record"> {{ (record.cpu ? record.cpu : 0) + 'C/' + (record.memory ? record.memory : 0) + 'GB/' }}{{ record.disk == null ? 0 : record.disk + 'GB' }} </template>
|
||||
<template #status="status">
|
||||
<status-icon :type="status | vmStatusColor">{{ status | openstackServer }}</status-icon>
|
||||
</template>
|
||||
<template #memory="val"> {{ val }} GB </template>
|
||||
</AdvanceTable>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCloudVendor } from 'services/platform/index'
|
||||
import { refreshGlobalResourceMonitoring, downloadGlobalResourceList, getGlobalResourceMonitoringCount } from '@/services/soa/costAnalysis'
|
||||
import { getPolicyVms } from '@/services/monitor'
|
||||
import { getProject } from 'services/system/project'
|
||||
import dayjs from 'utils/day'
|
||||
import Block from './components/Block.js'
|
||||
export default {
|
||||
mixins: [Block],
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
width: '160px',
|
||||
label: '资源名称',
|
||||
prop: 'name'
|
||||
},
|
||||
{
|
||||
width: '200px',
|
||||
label: 'IP地址',
|
||||
prop: 'privateIps',
|
||||
sortable: 'custom',
|
||||
scopedSlots: { customRender: 'ip' }
|
||||
},
|
||||
{
|
||||
width: '120px',
|
||||
label: '规格',
|
||||
prop: 'spec',
|
||||
scopedSlots: { customRender: 'spec' }
|
||||
},
|
||||
{
|
||||
width: '110px',
|
||||
label: '存储总容量(GB)',
|
||||
prop: 'disk'
|
||||
},
|
||||
{
|
||||
label: '运行状态',
|
||||
prop: 'status',
|
||||
scopedSlots: { customRender: 'status' }
|
||||
},
|
||||
|
||||
{
|
||||
width: '90px',
|
||||
label: 'CPU利用率',
|
||||
prop: 'cpuUsage'
|
||||
},
|
||||
{
|
||||
width: '90px',
|
||||
label: '内存利用率',
|
||||
prop: 'memUsage'
|
||||
},
|
||||
{
|
||||
width: '140px',
|
||||
label: ' 磁盘读取速率(KB/s)',
|
||||
prop: 'diskRead'
|
||||
},
|
||||
{
|
||||
width: '140px',
|
||||
label: ' 磁盘写入速率(KB/s)',
|
||||
prop: 'diskWrite'
|
||||
},
|
||||
{
|
||||
width: '110px',
|
||||
label: '总成本消耗(元)',
|
||||
prop: 'totalCost'
|
||||
},
|
||||
{
|
||||
width: '115px',
|
||||
label: '当月成本消耗(元)',
|
||||
prop: 'monthCost'
|
||||
},
|
||||
{
|
||||
width: '115px',
|
||||
label: '季度成本消耗(元)',
|
||||
prop: 'quarterCost'
|
||||
},
|
||||
{
|
||||
width: '115px',
|
||||
label: '当年成本消耗(元)',
|
||||
prop: 'yearCost'
|
||||
},
|
||||
{
|
||||
label: '所属平台',
|
||||
prop: 'vendorName'
|
||||
},
|
||||
{
|
||||
width: '115px',
|
||||
label: '所属项目',
|
||||
prop: 'projectName'
|
||||
},
|
||||
{
|
||||
label: '所属用户',
|
||||
prop: 'ownerName'
|
||||
},
|
||||
{
|
||||
width: '160px',
|
||||
label: '创建时间',
|
||||
prop: 'gmtCreate'
|
||||
},
|
||||
{
|
||||
width: '140px',
|
||||
label: '运行时长',
|
||||
prop: 'duration'
|
||||
}
|
||||
],
|
||||
searchConfigs: [
|
||||
{ label: '资源名称', value: 'name', type: 'Input' },
|
||||
{ label: 'IP', value: 'privateIps', type: 'Input' },
|
||||
{ type: 'Select', label: '所属平台', value: 'vendorId', data: [], service: { api: getCloudVendor, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ type: 'Select', label: '所属项目', value: 'projectId', data: [], service: { api: getProject, params: { simple: true }, attr: 'data.rows' } },
|
||||
{ value: 'isTemplate', type: 'Const', initValue: false }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getData() {
|
||||
getGlobalResourceMonitoringCount(this.params).then((res) => {
|
||||
setTimeout(() => {
|
||||
this.GeneratorBlockComponent([
|
||||
{
|
||||
name: '总成本消耗',
|
||||
value: res.data?.totle ?? '--',
|
||||
unit: '元'
|
||||
},
|
||||
{
|
||||
name: '当月成本消耗',
|
||||
value: res.data?.month ?? '--',
|
||||
unit: '元'
|
||||
},
|
||||
{
|
||||
name: '季度成本消耗',
|
||||
value: res.data?.quarter ?? '--',
|
||||
unit: '元'
|
||||
},
|
||||
{
|
||||
name: '当年成本消耗',
|
||||
value: res.data?.year ?? '--',
|
||||
unit: '元'
|
||||
}
|
||||
])
|
||||
}, 500)
|
||||
})
|
||||
getPolicyVms(this.params).then((res) => {
|
||||
if (res.success) {
|
||||
this.list = res.data.rows.map((item) => {
|
||||
if (item.privateIps) item.privateIpsList = JSON.parse(item.privateIps)
|
||||
if (item.publicIps) item.publicIps = JSON.parse(item.publicIps)
|
||||
if (item.vendorType === 'SANGFOR' && item.privateIps) {
|
||||
item.privateIps = Object.values(JSON.parse(item.privateIps))
|
||||
.map((item) => Object.keys(item)[0])
|
||||
.toString()
|
||||
}
|
||||
// 当前时间 - 创建时间 = 运行时长
|
||||
// xx 天 xx 小时
|
||||
item.duration = item.gmtCreate ? dayjs().diff(dayjs(item.gmtCreate), 'day') + '天' + dayjs().diff(dayjs(item.gmtCreate), 'hour') + '小时' : '虚拟机不存在或已被删除'
|
||||
return item
|
||||
})
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handllExport() {
|
||||
downloadGlobalResourceList(this.params)
|
||||
},
|
||||
handllRefresh() {
|
||||
refreshGlobalResourceMonitoring()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './components/Block.scss';
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<AdvanceTable title="新增资源报表" :isInitSearch="false" :searchConfigs="searchConfigs" :data="list" :params="params" :columns="columns" :get-list="getData" :total="total">
|
||||
<template v-slot:action>
|
||||
<TimeSelect ref="timeSelectRef" @success="getData">
|
||||
<el-button type="primary" @click="handllExport()"> 导出 </el-button>
|
||||
</TimeSelect>
|
||||
</template>
|
||||
</AdvanceTable>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getNewResourceList, downloadNewResourceList } from '@/services/soa/costAnalysis'
|
||||
import Block from './components/Block.js'
|
||||
import TimeSelect from './components/TimeSelect.vue'
|
||||
export default {
|
||||
mixins: [Block],
|
||||
components: { TimeSelect },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
params: {
|
||||
page: 1,
|
||||
rows: 10
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
label: '月份',
|
||||
prop: 'month'
|
||||
},
|
||||
{
|
||||
label: '总增加成本(元)',
|
||||
prop: 'totalMoney'
|
||||
},
|
||||
{
|
||||
label: 'CPU增加量(核)',
|
||||
prop: 'totalCpu'
|
||||
},
|
||||
{
|
||||
label: '内存增加量(GB)',
|
||||
prop: 'totalMemory'
|
||||
},
|
||||
{
|
||||
label: '存储增加量(GB)',
|
||||
prop: 'totalDisk'
|
||||
},
|
||||
{
|
||||
label: '虚拟机增加量(台)',
|
||||
prop: 'totalServer'
|
||||
}
|
||||
],
|
||||
searchConfigs: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getData()
|
||||
},
|
||||
methods: {
|
||||
getData() {
|
||||
const params = this.$refs.timeSelectRef.params
|
||||
if (params.type !== 'Years' && params.year && !params.month) return this.$message.error('请选择月份')
|
||||
getNewResourceList({ page: this.params.page, rows: this.params.rows, time: JSON.stringify(params.time) }).then((res) => {
|
||||
setTimeout(() => {
|
||||
this.GeneratorBlockComponent([
|
||||
{
|
||||
name: '总增加成本',
|
||||
value: res.data?.money ?? '--',
|
||||
unit: '元'
|
||||
},
|
||||
{
|
||||
name: 'CPU增加量',
|
||||
value: res.data?.cpu ?? '--',
|
||||
unit: '核'
|
||||
},
|
||||
{
|
||||
name: '内存增加量',
|
||||
value: res.data?.memory ?? '--',
|
||||
unit: 'GB'
|
||||
},
|
||||
{
|
||||
name: '存储增加量',
|
||||
value: res.data?.disk ?? '--',
|
||||
unit: 'GB'
|
||||
}
|
||||
])
|
||||
}, 500)
|
||||
if (res.success) {
|
||||
this.list = res.data.list
|
||||
this.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
handllExport() {
|
||||
const params = this.$refs.timeSelectRef.params
|
||||
if (params.type !== 'Years' && params.year && !params.month) return this.$message.error('请选择月份')
|
||||
downloadNewResourceList({ page: this.params.page, rows: this.params.rows, time: JSON.stringify(params.time) })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import './components/Block.scss';
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
import Vue from 'vue'
|
||||
let componentInstance,
|
||||
MyComponent = null
|
||||
export default {
|
||||
data() {},
|
||||
beforeDestroy() {
|
||||
if (componentInstance?.$destroy) {
|
||||
componentInstance.$destroy()
|
||||
}
|
||||
MyComponent = null
|
||||
componentInstance = null
|
||||
},
|
||||
methods: {
|
||||
CreateComponent(countData) {
|
||||
return Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
countData: countData
|
||||
}
|
||||
},
|
||||
render(h) {
|
||||
return h(
|
||||
'el-row',
|
||||
{
|
||||
domProps: {
|
||||
id: 'cus-block'
|
||||
},
|
||||
props: {
|
||||
gutter: 10
|
||||
},
|
||||
class: ['m-b']
|
||||
},
|
||||
this.countData.map((item) =>
|
||||
h(
|
||||
'el-col',
|
||||
{
|
||||
props: {
|
||||
span: 6
|
||||
}
|
||||
},
|
||||
[h('div', { class: ['text-center', 'item'] }, [h('div', item.name), h('div', item.value + item.unit)])]
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
GeneratorBlockComponent(countData) {
|
||||
if (!componentInstance) {
|
||||
MyComponent = this.CreateComponent(countData)
|
||||
// 现在,我们创建一个实例
|
||||
componentInstance = new MyComponent()
|
||||
// 挂载实例到文档之外的元素上
|
||||
componentInstance.$mount()
|
||||
} else {
|
||||
componentInstance.countData = countData
|
||||
}
|
||||
this.insertComponent()
|
||||
},
|
||||
insertComponent() {
|
||||
// 找到需要插入新组件的DOM元素
|
||||
const referenceElement = document.getElementsByClassName('table-tools')[0]
|
||||
if (!referenceElement) return
|
||||
// 把Vue组件的DOM插入到找到的DOM元素之后
|
||||
referenceElement.parentNode.insertBefore(componentInstance.$el, referenceElement.nextSibling)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
::v-deep #cus-block {
|
||||
.item {
|
||||
color: #fff;
|
||||
background-color: #5a94ff;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<el-form inline :model="params">
|
||||
<el-form-item>
|
||||
<slot></slot>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-radio-group v-model="params.type" @change="typeChange">
|
||||
<el-radio-button v-for="item in timeList" :key="item.value" :label="item.value">{{ item.label }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="params.year" placeholder="请选择年份" @change="yearChange">
|
||||
<el-option v-for="item in yearList" :key="item" :label="item" :value="item"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="params.month" placeholder="请选择" :disabled="!params.year" @change="monthChange" v-show="params.type !== 'Years'">
|
||||
<el-option v-for="item in monthList" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'utils/day'
|
||||
const currentYear = dayjs().year() // 获取当前年份
|
||||
const lastFiveYears = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
lastFiveYears.push(currentYear - i)
|
||||
}
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
params: {
|
||||
type: 'Months',
|
||||
year: '',
|
||||
month: '',
|
||||
time: {
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
}
|
||||
},
|
||||
timeList: [
|
||||
{ label: '月', value: 'Months' },
|
||||
{ label: '季度', value: 'QuarterYears' },
|
||||
{ label: '半年度', value: 'HalfYears' },
|
||||
{ label: '年度', value: 'Years' }
|
||||
],
|
||||
yearList: lastFiveYears,
|
||||
monthList: [],
|
||||
interval: 1
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
typeChange() {
|
||||
this.params.month = ''
|
||||
this.params.year = ''
|
||||
this.params.time = {
|
||||
startTime: '',
|
||||
endTime: ''
|
||||
}
|
||||
if (this.params.type === 'Months') this.$emit('success')
|
||||
},
|
||||
monthChange() {
|
||||
this.formatterTime()
|
||||
},
|
||||
yearChange() {
|
||||
this.params.month = ''
|
||||
this.generaterOptions()
|
||||
if (this.params.type === 'Years') {
|
||||
this.formatterTime()
|
||||
}
|
||||
},
|
||||
generaterOptions() {
|
||||
switch (this.params.type) {
|
||||
case 'Months':
|
||||
this.interval = 1
|
||||
// 根据当前年份生成月份列表
|
||||
this.monthList = Array.from({ length: 12 }, (v, k) => {
|
||||
const month = k + 1
|
||||
return {
|
||||
label: `${month}月`,
|
||||
value: month < 10 ? `0${month}` : `${month}`
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'QuarterYears':
|
||||
this.interval = 3
|
||||
this.monthList = [
|
||||
{ label: '一季度', value: '01' },
|
||||
{ label: '二季度', value: '04' },
|
||||
{ label: '三季度', value: '07' },
|
||||
{ label: '四季度', value: '10' }
|
||||
]
|
||||
break
|
||||
case 'HalfYears':
|
||||
this.interval = 6
|
||||
this.monthList = [
|
||||
{ label: '1-6月', value: '01' },
|
||||
{ label: '7-12月', value: '07' }
|
||||
]
|
||||
break
|
||||
case 'Years':
|
||||
this.interval = 12
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
formatterTime() {
|
||||
if (!this.params.month) this.params.month = '01'
|
||||
let nextMonth = parseInt(this.params.month) + this.interval - 1
|
||||
const currentYear = this.params.year
|
||||
const startTime = `${currentYear}-${this.params.month}-01 00:00:00`
|
||||
if (nextMonth < 10) {
|
||||
nextMonth = '0' + nextMonth
|
||||
} else if (nextMonth > 12) {
|
||||
// 取年末
|
||||
nextMonth = '12'
|
||||
}
|
||||
// 取月份的最后一天
|
||||
const lastDay = dayjs(`${currentYear}-${nextMonth}-01`).endOf('month').format('DD')
|
||||
this.params.time = {
|
||||
startTime,
|
||||
endTime: `${currentYear}-${nextMonth}-${lastDay} 23:59:59`
|
||||
}
|
||||
this.$emit('success')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-form-item--mini.el-form-item,
|
||||
.el-form-item--small.el-form-item {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<div>
|
||||
<basic-form label-width="0px" ref="data" :disabled="disabled">
|
||||
<PreviewItem :item-data="itemData"></PreviewItem>
|
||||
<basic-form-item>
|
||||
<el-row>
|
||||
<el-col :span="24" v-for="(cell, index) in disks" :key="index" class="m-t">
|
||||
<el-col :span="2" v-if="!disabled">
|
||||
<el-button :disabled="disks.length == 1" class="m-l-sm" type="danger" icon="el-icon-delete" @click="removeItem(index)"></el-button>
|
||||
</el-col>
|
||||
<el-col :span="18"> <el-input-number :min="1" v-model="cell.disk" class="w m-r"> </el-input-number> GB </el-col>
|
||||
</el-col>
|
||||
<!-- <basic-form-item label="名称:" prop="name" validate="required">
|
||||
<el-input v-model="cell.name"></el-input>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="容量:" prop="size" validate="required">
|
||||
<el-input v-model="cell.size"></el-input>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="总线:" prop="bus" validate="required">
|
||||
<el-select filterable v-model="cell.bus" clearable>
|
||||
<el-option v-for="(item, index) in busData" :key="index" :label="item.name" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="存储策略:" prop="volumeTemplateId" validate="required">
|
||||
<el-select filterable v-model="cell.volumeTemplateId" clearable>
|
||||
<el-option v-for="(item, index) in templateData" :key="index" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item> -->
|
||||
<el-col v-if="!disabled" :span="4" class="add-border m-t-md" @click.native="addItem()">
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form-item>
|
||||
</basic-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCategoriesByCode } from 'services/services/product'
|
||||
import { getDictChildren } from 'services/platform/index'
|
||||
import PreviewItem from './PreviewItem.vue'
|
||||
import { getVolumeTpl } from 'services/platform/smart'
|
||||
export default {
|
||||
components: { PreviewItem },
|
||||
props: {
|
||||
itemData: {
|
||||
type: Object
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
categoryList: [],
|
||||
disks: [],
|
||||
templateData: [],
|
||||
busData: [
|
||||
{ name: 'VIRTIO', value: 'VIRTIO' },
|
||||
{ name: 'SCSI', value: 'SCSI' },
|
||||
{ name: 'IDE', value: 'IDE' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
categoryMap() {
|
||||
const map = {}
|
||||
this.categoryList.forEach(item => {
|
||||
map[item.id] = item
|
||||
})
|
||||
return map
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getTemplateData()
|
||||
this.getCategoryList()
|
||||
if (this.itemData.upgrade) {
|
||||
this.itemData.addDataDisks.forEach((item, index) => {
|
||||
this.disks.push({
|
||||
...item,
|
||||
...this.itemData.upgrade[index]
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTemplateData() {
|
||||
getVolumeTpl({
|
||||
simple: true,
|
||||
params: this.$tools.handleSearchParam({
|
||||
vendorId: this.itemData.vendorId
|
||||
})
|
||||
}).then(data => {
|
||||
if (data.success) {
|
||||
this.templateData = data.data.rows
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取服务类型
|
||||
getCategoryList() {
|
||||
getCategoriesByCode('cloudtower.standard.volume').then(data => {
|
||||
if (data.success) {
|
||||
if (!data.data.length) return this.$message.error('该服务不存在产品类型请检查')
|
||||
this.categoryList = data.data
|
||||
if (!this.itemData.upgrade) this.addItem()
|
||||
}
|
||||
})
|
||||
},
|
||||
removeItem(index) {
|
||||
this.disks.splice(index, 1)
|
||||
},
|
||||
addItem() {
|
||||
const [{ id: categoryId }] = this.categoryList
|
||||
const result = {
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
categoryId,
|
||||
disk: 1
|
||||
}
|
||||
this.disks.push(result)
|
||||
},
|
||||
ok() {
|
||||
const { service, instance, instanceName, expiredTime, params, preview } = this.itemData
|
||||
const obj = {
|
||||
preview,
|
||||
params,
|
||||
service,
|
||||
instance,
|
||||
instanceName,
|
||||
operation: 'AddDisk',
|
||||
upgrade: [],
|
||||
expiredTime,
|
||||
addDataDisks: []
|
||||
}
|
||||
|
||||
this.disks.forEach(item => {
|
||||
const { categoryId, serviceCode, disk } = item
|
||||
const category = this.categoryMap[categoryId]?.remark
|
||||
obj.upgrade.push({
|
||||
serviceCode,
|
||||
categoryId,
|
||||
category,
|
||||
specs: [
|
||||
{
|
||||
disk
|
||||
}
|
||||
]
|
||||
})
|
||||
obj.addDataDisks.push({
|
||||
category,
|
||||
disk,
|
||||
type: 'newDisk',
|
||||
bus: this.busData[0].value,
|
||||
volumeTemplateId: this.templateData[0].id, // 存储策略
|
||||
volumeTemplateName: this.templateData[0].name
|
||||
})
|
||||
})
|
||||
return obj
|
||||
},
|
||||
getApplyData() {
|
||||
const obj = {
|
||||
...this.itemData,
|
||||
upgrade: [],
|
||||
addDataDisks: []
|
||||
}
|
||||
this.disks.forEach(item => {
|
||||
const { categoryId, serviceCode, disk } = item
|
||||
const category = this.categoryMap[categoryId]?.remark
|
||||
obj.upgrade.push({
|
||||
serviceCode,
|
||||
categoryId,
|
||||
category,
|
||||
specs: [
|
||||
{
|
||||
disk
|
||||
}
|
||||
]
|
||||
})
|
||||
obj.addDataDisks.push({
|
||||
category,
|
||||
disk,
|
||||
type: 'newDisk',
|
||||
bus: this.busData[0].value,
|
||||
volumeTemplateId: this.templateData[0].id, // 存储策略
|
||||
volumeTemplateName: this.templateData[0].name
|
||||
})
|
||||
})
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-border {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
border: 1px dashed black;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -32,8 +32,8 @@ import UpgradePublicSpec from './UpgradePublicSpec.vue'
|
|||
import { defineComponent, ref } from '@vue/composition-api'
|
||||
import { componentMap } from './config'
|
||||
|
||||
function getApplicationComponent(service: string) {
|
||||
if (service.includes('server')) {
|
||||
function getApplicationComponent(service: string = '', workOrderTypeCode: string) {
|
||||
if (service.includes('server') || workOrderTypeCode === 'CloudServerApplication') {
|
||||
return 'VmNode'
|
||||
}
|
||||
if (service.includes('volume')) {
|
||||
|
@ -96,7 +96,10 @@ export default defineComponent({
|
|||
function getAlterationComponent() {
|
||||
let { operation, preview, service } = itemData.value
|
||||
if (operation == 'AddDisk') {
|
||||
return 'AddDisk'
|
||||
if (operation === 'AddDisk') {
|
||||
if (service === 'cloudtower.standard.server') return 'AddDiskCT'
|
||||
return 'AddDisk'
|
||||
}
|
||||
}
|
||||
if (operation == 'EditDisk') return 'EditDisk'
|
||||
if (operation == 'Upgrade' && (service == 'tencent.standard.server' || service == 'aliyun.standard.server' || service == 'aws.standard.server' || service == 'huawei.standard.server')) return 'UpgradePublicSpec'
|
||||
|
@ -109,10 +112,11 @@ export default defineComponent({
|
|||
}
|
||||
// 当前节点
|
||||
const currentComponent = ref('')
|
||||
const { service } = itemData.value
|
||||
const { service, workOrderTypeCode } = itemData.value
|
||||
switch (ApplyCategory) {
|
||||
case 'ApplicationOperate':
|
||||
currentComponent.value = getApplicationComponent(service)
|
||||
case 'EFCApplicationOperate':
|
||||
currentComponent.value = getApplicationComponent(service, workOrderTypeCode)
|
||||
break
|
||||
case 'UnsubscribeOperate':
|
||||
currentComponent.value = 'PreviewItem'
|
||||
|
|
|
@ -206,7 +206,7 @@ export default {
|
|||
data.forEach((item) => {
|
||||
// 忽略此elements
|
||||
if (item.ignore) return
|
||||
const { serviceCode, skuId, insAmount, elements, categoryId, categoryMap = {} } = item
|
||||
const { serviceCode, skuId, insAmount, elements, categoryId, categoryMap = {}, skuList } = item
|
||||
// 如果存在子节点对子节点进行处理
|
||||
if (elements) {
|
||||
const returnData = this.getSkuParams(
|
||||
|
@ -233,6 +233,7 @@ export default {
|
|||
specs = [{ disk: insAmount }]
|
||||
break
|
||||
case 'smartx.standard.volume':
|
||||
case 'cloudtower.standard.volume':
|
||||
if (item.name == '系统盘') {
|
||||
specs = [{ disk: insAmount }]
|
||||
result.props = {
|
||||
|
@ -247,7 +248,7 @@ export default {
|
|||
default:
|
||||
specs = this.getSpec(item)
|
||||
}
|
||||
if (item.name == '系统盘' && item.serviceCode == 'smartx.standard.volume') {
|
||||
if (item.name == '系统盘' && item.serviceCode == 'cloudtower.standard.volume') {
|
||||
const arr = item.skuList.map((sku) => {
|
||||
return Number(sku.spec[0].specValue)
|
||||
})
|
||||
|
@ -257,6 +258,17 @@ export default {
|
|||
}
|
||||
result.specs = specs
|
||||
}
|
||||
if (item.serviceCode === 'sangfor.standard.volume' && item.name === '系统盘') {
|
||||
this.addData.configs.disks.forEach((diskItem, index) => {
|
||||
const {
|
||||
id,
|
||||
spec: [{ specValue }]
|
||||
} = skuList.find((sku) => sku.id === diskItem.size_mb)
|
||||
this.$nextTick(() => {
|
||||
diskItem.size_mb = specValue * 1024
|
||||
})
|
||||
})
|
||||
}
|
||||
params.push(result)
|
||||
})
|
||||
return params
|
||||
|
|
|
@ -177,10 +177,13 @@ export default {
|
|||
}).then((data) => {
|
||||
if (data.success) {
|
||||
this.cacheAz = this.addData.location.az
|
||||
const { vendorId, id, networkRelations } = data.data
|
||||
const { vendorId, id, networkRelations, azUuid, groupId } = data.data
|
||||
this.addData.location.vendorId = vendorId
|
||||
this.addData.location.poolGroupId = id
|
||||
if (this.addData.location.vendorType == 'VMWARE' || this.addData.location.vendorType == 'SMARTX' || this.addData.location.vendorType == 'FUSIONSPHERE') this.addData.networkRelations = networkRelations
|
||||
this.addData.location.azUuid = azUuid
|
||||
this.addData.location.groupUuid = groupId
|
||||
if (['VMWARE', 'SANGFOR', 'CLOUDTOWER', 'CNWARE'].includes(this.addData.location.vendorType)) this.addData.networkRelations = networkRelations
|
||||
|
||||
// if (hasNsx) this.$set(this.addData.location, 'hasNsx', hasNsx)
|
||||
this.$emit('changeVendorId', vendorId, az)
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import { cloneDeep } from 'lodash-es'
|
||||
import { nanoid } from 'nanoid'
|
||||
export const GEN_UUID = () => nanoid(8)
|
||||
const defaultCpu = 1
|
||||
const defaultMemory = 1
|
||||
const defaultSysDisk = 50
|
||||
|
||||
export const subApplicationParam = {
|
||||
taskGroupUuid: GEN_UUID(),
|
||||
taskTargetUuid: GEN_UUID(),
|
||||
networkRelations: [],
|
||||
imageData: {},
|
||||
osList: [],
|
||||
versionList: [],
|
||||
subLocation: {
|
||||
region: '',
|
||||
az: '',
|
||||
vendorId: '',
|
||||
poolGroupId: '',
|
||||
vendorType: '',
|
||||
purpose: '',
|
||||
remark: '',
|
||||
resourceLabel: []
|
||||
},
|
||||
emption: {
|
||||
duration: {
|
||||
mode: 'Hour',
|
||||
amount: 1
|
||||
},
|
||||
count: 1
|
||||
},
|
||||
configs: {
|
||||
// 云主机信息相关
|
||||
name: '',
|
||||
nameRuleId: '',
|
||||
vmHostName: '',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
|
||||
// 镜像相关
|
||||
osCategory: '',
|
||||
osVersion: '',
|
||||
imageId: '',
|
||||
templateDisk: '', // 前端用
|
||||
|
||||
// 规格相关
|
||||
categoryId: '',
|
||||
cpu: defaultCpu,
|
||||
memory: defaultMemory,
|
||||
|
||||
// 系统盘
|
||||
sysDisk: {
|
||||
categoryId: '',
|
||||
disk: defaultSysDisk
|
||||
},
|
||||
// 数据盘
|
||||
addDiskList: [],
|
||||
|
||||
// 网卡相关
|
||||
networkCardConfigs: [
|
||||
{
|
||||
networkCardId: '',
|
||||
ipPolicy: 'Manual',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
portGroupId: '',
|
||||
address: [],
|
||||
mask: '',
|
||||
gateway: '',
|
||||
dns: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
isLoadData: true,
|
||||
serviceCode: '',
|
||||
categoryId: '',
|
||||
category: '',
|
||||
specs: [
|
||||
{
|
||||
cpu: defaultCpu
|
||||
},
|
||||
{
|
||||
memory: defaultMemory
|
||||
}
|
||||
],
|
||||
serviceItem: {}
|
||||
},
|
||||
{
|
||||
isLoadData: true,
|
||||
serviceCode: '',
|
||||
categoryId: '',
|
||||
category: '',
|
||||
specs: [
|
||||
{
|
||||
disk: defaultSysDisk
|
||||
}
|
||||
],
|
||||
serviceItem: {}
|
||||
}
|
||||
],
|
||||
service: ''
|
||||
}
|
||||
export const addNew = {
|
||||
workOrderTypeCode: 'CloudServerApplication', // 工单类型编号
|
||||
location: {
|
||||
name: '',
|
||||
businessId: '',
|
||||
businessName: '',
|
||||
projectId: '',
|
||||
projectName: '',
|
||||
creatorId: '',
|
||||
creatorName: '',
|
||||
menderId: '',
|
||||
menderName: '',
|
||||
ownerId: '',
|
||||
ownerName: '',
|
||||
tenantId: '',
|
||||
remark: ''
|
||||
},
|
||||
subApplicationParams: [cloneDeep(subApplicationParam)],
|
||||
tasks: []
|
||||
}
|
||||
export const defaultTask = {
|
||||
sceneId: '',
|
||||
templateId: '',
|
||||
taskGroupUuid: '',
|
||||
taskName: '',
|
||||
taskCode: 'standard'
|
||||
}
|
||||
export const ServiceCodeMap = {
|
||||
server: {
|
||||
VMWARE: 'vmware.standard.server',
|
||||
CNWARE: 'cnware.standard.server',
|
||||
ZSTACK: 'zstack.standard.server',
|
||||
CLOUDTOWER: 'cloudtower.standard.server',
|
||||
SANGFOR: 'sangfor.standard.server',
|
||||
CECSTACK: 'cecstack.standard.server',
|
||||
INSPURRAIL: 'inspurrail.standard.server'
|
||||
},
|
||||
disk: {
|
||||
VMWARE: 'vmware.storage.disk',
|
||||
CNWARE: 'cnware.standard.volume',
|
||||
ZSTACK: 'zstack.storage.disk',
|
||||
CLOUDTOWER: 'cloudtower.standard.volume',
|
||||
SANGFOR: 'sangfor.storage.disk',
|
||||
CECSTACK: 'cecstack.standard.volume',
|
||||
INSPURRAIL: 'inspurrail.standard.volume'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
/** * Created by HaijunZhang on 2019/4/28. */
|
||||
<template>
|
||||
<common-wrapper :add-data="addData" @vendorId="getList" ref="common" vendorType="OPENSTACK" :elements="elements" :loading="loading" :get-params="getParams" :item-data="retention" :disabled="disabled" v-bind="$attrs">
|
||||
<div class="item-block">
|
||||
<h5>配置信息</h5>
|
||||
<basic-form-item label="硬盘类型:">
|
||||
<el-radio-group v-model="currentElement.categoryId" @change="getSku(currentElement)">
|
||||
<el-radio-button :label="item.id" v-for="(item, index) in currentElement.categoryList" :key="index">{{ item.name }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="配置:" validate="required" v-if="addData.configs.disktype == '空白卷'">
|
||||
<sku-table :skus="currentElement.skuList" style="max-width: 600px" :mode="addData.emption.duration.mode" :show-price="true" :column-props="[{ label: '磁盘(GB)', value: 'disk' }]">
|
||||
<el-table-column show-overflow-tooltip label="规格名称" prop="name">
|
||||
<template v-slot="scope">
|
||||
<el-radio v-model="currentElement.skuId" :label="scope.row.id">{{ scope.row.code }}</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</sku-table>
|
||||
</basic-form-item>
|
||||
<basic-form :model="addData.configs" ref="addForm" label-position="left">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<basic-form-item label="名称:" validate="required" prop="name">
|
||||
<el-input v-model="addData.configs.name" placeholder="请输入名称" class="basic-cmp"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<basic-form-item label="卷来源:" required>
|
||||
<el-radio-group v-model="addData.configs.disktype" size="small" @change="chooseOrginWay">
|
||||
<el-radio :label="item.name" border v-for="(item, index) in typeList" :key="index">{{ item.name }}</el-radio>
|
||||
</el-radio-group>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" v-if="addData.configs.disktype == '镜像'" key="1">
|
||||
<el-col :span="10">
|
||||
<basic-form-item label="系统镜像:" prop="sourceImgId" validate="required">
|
||||
<el-select filterable v-model="addData.configs.sourceImgId">
|
||||
<el-option v-for="(item, index) in imageList" :key="index" :label="item.name + '(最小磁盘:' + item.minDisk + 'GB)'" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" v-else-if="addData.configs.disktype == '卷'" key="2">
|
||||
<el-col :span="10">
|
||||
<basic-form-item label="选择卷:" prop="sourceValidId" validate="required">
|
||||
<el-select filterable v-model="addData.configs.sourceValidId" @change="changeVolumeSize">
|
||||
<el-option v-for="(item, index) in volumeList" :key="index" :label="`${item.name}(${item.size}GB)`" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20" v-else-if="addData.configs.disktype == '快照'" key="3">
|
||||
<el-col :span="10">
|
||||
<basic-form-item label="选择快照:" prop="sourceSnapId" validate="required">
|
||||
<el-select filterable v-model="addData.configs.sourceSnapId" @change="changeSnapshotSize">
|
||||
<el-option v-for="(item, index) in snapshotList" :key="index" :label="`${item.name}(${item.volume.size}GB)`" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-else></el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<basic-form-item label="描述:" prop="remark">
|
||||
<el-input type="textarea" v-model="addData.configs.remark" placeholder="请输入描述"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form>
|
||||
</div>
|
||||
<div slot="preview-card">
|
||||
<basic-form-item label="磁盘容量:">{{ addData.configs.size }}GB</basic-form-item>
|
||||
</div>
|
||||
</common-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonWrapper from 'views/resource-apply/components/CommonWrapper.vue'
|
||||
import sku from './../mixins/sku'
|
||||
import create from '../mixins/create'
|
||||
import { add, element } from './../data/init'
|
||||
import { getSnapshot, getVolume, conditionImage } from 'services/platform/index'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { getSpecValue } from 'views/resource-apply/utils/index'
|
||||
import { getShoppingCartDetail } from 'services/system/shop_cart'
|
||||
|
||||
export default {
|
||||
components: { CommonWrapper },
|
||||
mixins: [sku, create],
|
||||
props: {
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
itemData: {
|
||||
type: [Object, Boolean]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.itemData) {
|
||||
this.retention = this.itemData
|
||||
this.addData = cloneDeep(this.retention)
|
||||
this.handleShowData()
|
||||
} else if (this.$route.query.id) {
|
||||
getShoppingCartDetail(this.$route.query.id).then((data) => {
|
||||
if (data.success) {
|
||||
this.retention = JSON.parse(data.data.inventory)
|
||||
this.addData = cloneDeep(this.retention)
|
||||
this.handleShowData()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elements: [
|
||||
{
|
||||
...element,
|
||||
name: '云硬盘',
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
main: true
|
||||
}
|
||||
],
|
||||
addData: {
|
||||
...cloneDeep(add),
|
||||
location: {
|
||||
...add.location,
|
||||
vendorType: this.type,
|
||||
diskCategory: ''
|
||||
},
|
||||
service: 'cloudtower.standard.volume',
|
||||
configs: {
|
||||
resourceLabel: [],
|
||||
diskCategoryId: 0,
|
||||
sourceImgId: '',
|
||||
sourceValidId: '',
|
||||
sourceSnapId: '',
|
||||
size: 10,
|
||||
disktype: '空白卷'
|
||||
}
|
||||
},
|
||||
typeList: [{ name: '空白卷' }, { name: '卷' }, { name: '快照' }],
|
||||
snapshotList: [],
|
||||
volumeList: [],
|
||||
imageList: [],
|
||||
retention: false,
|
||||
skuList: [],
|
||||
flag: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentElement() {
|
||||
return this.elements[0]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleShowData() {
|
||||
const { elements } = this.retention
|
||||
const [first, ...others] = elements
|
||||
this.elements = [
|
||||
{
|
||||
...element,
|
||||
...first,
|
||||
insAmount: this.addData.configs.size
|
||||
}
|
||||
]
|
||||
this.initLoad()
|
||||
},
|
||||
getPostData() {
|
||||
let data = false
|
||||
data = this.$refs.common.handlePostData()
|
||||
return data
|
||||
},
|
||||
getParams() {
|
||||
let data = false
|
||||
this.$refs.addForm.validate((valid) => {
|
||||
if (valid) {
|
||||
const { disktype } = this.addData.configs
|
||||
this.addData.configs.size = getSpecValue(this.currentElement).disk
|
||||
let size = 0
|
||||
if (disktype == '卷') size = this.volumeList.find((item) => item.id == this.addData.configs.sourceValidId).size
|
||||
if (disktype == '快照') size = this.snapshotList.find((item) => item.id == this.addData.configs.sourceSnapId).volume.size
|
||||
if (disktype !== '空白卷') {
|
||||
this.addData.configs.size = size
|
||||
}
|
||||
if (disktype == '空白卷' && this.addData.configs.size < size) {
|
||||
this.$message.error('所选规格不可小于所选卷或快照的规格')
|
||||
data = false
|
||||
} else {
|
||||
data = true
|
||||
}
|
||||
}
|
||||
})
|
||||
return data
|
||||
},
|
||||
getList() {
|
||||
this.getSnapshotData()
|
||||
this.getVolumeData()
|
||||
// this.getImageData()
|
||||
},
|
||||
changeVolumeSize(val) {
|
||||
const data = this.volumeList.find((item) => item.id == val)
|
||||
this.currentElement.skuList[0].spec = [{ specName: 'disk', specValue: data?.size, unit: 'GB' }]
|
||||
this.currentElement.skuId = this.addData.serviceItem.id
|
||||
},
|
||||
changeSnapshotSize(val) {
|
||||
const data = this.snapshotList.find((item) => item.id == val)
|
||||
this.currentElement.skuList[0].spec = [{ specName: 'disk', specValue: data?.volume?.size, unit: 'GB' }]
|
||||
this.currentElement.skuId = this.addData.serviceItem.id
|
||||
},
|
||||
chooseOrginWay(item) {
|
||||
let list = []
|
||||
if (this.flag) {
|
||||
this.skuList = this.currentElement.skuList
|
||||
this.flag = false
|
||||
}
|
||||
if (this.addData.configs.disktype == '镜像') {
|
||||
delete this.addData.configs.sourceValidId
|
||||
delete this.addData.configs.sourceSnapId
|
||||
this.currentElement.skuId = null
|
||||
} else if (this.addData.configs.disktype == '卷') {
|
||||
delete this.addData.configs.sourceImgId
|
||||
delete this.addData.configs.sourceSnapId
|
||||
this.currentElement.skuId = null
|
||||
list = []
|
||||
list.push(this.addData.serviceItem)
|
||||
this.currentElement.skuList = list
|
||||
} else if (this.addData.configs.disktype == '空白卷') {
|
||||
delete this.addData.configs.sourceValidId
|
||||
delete this.addData.configs.sourceImgId
|
||||
delete this.addData.configs.sourceSnapId
|
||||
this.currentElement.skuList = this.skuList
|
||||
} else {
|
||||
delete this.addData.configs.sourceImgId
|
||||
delete this.addData.configs.sourceValidId
|
||||
this.currentElement.skuId = null
|
||||
list = []
|
||||
list.push(this.addData.serviceItem)
|
||||
this.currentElement.skuList = list
|
||||
}
|
||||
},
|
||||
// 请求快照
|
||||
getSnapshotData() {
|
||||
const params = {
|
||||
page: 1,
|
||||
rows: 10000
|
||||
}
|
||||
const searchParam = [{ param: { vendorId: this.addData.location.vendorId, status: 'AVAILABLE', snapshotType: 'VOLUME' }, sign: 'EQ' }]
|
||||
params.params = JSON.stringify(searchParam)
|
||||
getSnapshot(params).then((data) => {
|
||||
if (data.success) {
|
||||
this.snapshotList = data.data.rows
|
||||
}
|
||||
})
|
||||
},
|
||||
// 请求云硬盘
|
||||
getVolumeData() {
|
||||
const params = {
|
||||
page: 1,
|
||||
rows: 9999
|
||||
}
|
||||
const searchParam = [{ param: { vendorId: this.addData.location.vendorId, status: 'AVAILABLE', tenantUuid: this.addData.tenantUuid }, sign: 'EQ' }]
|
||||
params.params = JSON.stringify(searchParam)
|
||||
getVolume(params).then((data) => {
|
||||
if (data.success) {
|
||||
this.volumeList = data.data.rows
|
||||
}
|
||||
})
|
||||
},
|
||||
// 请求镜像
|
||||
getImageData() {
|
||||
conditionImage({
|
||||
condition: 'listPublicImage',
|
||||
vendorId: this.addData.location.vendorId
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
this.imageList = data.data
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../index.scss';
|
||||
</style>
|
|
@ -11,6 +11,7 @@
|
|||
<azure :type="type" v-if="type == 'AZURE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></azure>
|
||||
<smart :type="type" v-if="type == 'SMARTX'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></smart>
|
||||
<fusion-sphere :type="type" v-if="type == 'FUSIONSPHERE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></fusion-sphere>
|
||||
<cloudTower :type="type" v-if="type == 'CLOUDTOWER'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></cloudTower>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -25,6 +26,8 @@ import aws from './aws.vue'
|
|||
import azure from './azure.vue'
|
||||
import smart from './smart.vue'
|
||||
import fusionSphere from './fusionSphere.vue'
|
||||
import cloudTower from './cloudTower.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
itemData: {
|
||||
|
@ -34,7 +37,7 @@ export default {
|
|||
type: Boolean
|
||||
}
|
||||
},
|
||||
components: { ali, op, huawei, tencent, aws, tce, azure, smart, fusionSphere, easyStack },
|
||||
components: { ali, op, huawei, tencent, aws, tce, azure, smart, fusionSphere, easyStack, cloudTower },
|
||||
data() {
|
||||
return {
|
||||
type: ''
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<template>
|
||||
<el-dialog title="磁盘配置" :visible.sync="dialog.visible" width="600px" append-to-body>
|
||||
<basic-form :model="configs" label-suffix=":" label-width="140px">
|
||||
<basic-form-item label="系统盘(GB)"> <el-input-number v-model="configs.sysDisk.disk" :min="configs.templateDisk" :max="1024"> </el-input-number> </basic-form-item>
|
||||
<basic-form-item label="数据盘(GB)">
|
||||
<div v-for="(item, index) in configs.addDiskList" :key="index" class="m-b">
|
||||
<el-input-number v-model="item.disk" :min="item.templateDisk || 10" :max="65536" class="m-r"> </el-input-number>
|
||||
<span class="tip" v-if="!!item.templateDisk">浪潮云模板磁盘,最小{{ item.templateDisk }}GB,不可删除</span>
|
||||
<el-button v-else type="text" @click="handleSub(index)">删除</el-button>
|
||||
</div>
|
||||
<el-button type="text" @click="handleAdd">增加</el-button>
|
||||
</basic-form-item>
|
||||
</basic-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click.native="dialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EFCDataDisk',
|
||||
props: {
|
||||
dialog: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
visible: false,
|
||||
index: '',
|
||||
row: {}
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
configs() {
|
||||
return this.dialog.row.configs
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
handleAdd() {
|
||||
this.configs.addDiskList.push({ disk: 50 })
|
||||
},
|
||||
handleSub(index) {
|
||||
this.configs.addDiskList.splice(index, 1)
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$emit('success', this.dialog.index)
|
||||
this.dialog.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,329 @@
|
|||
<template>
|
||||
<el-dialog title="网络配置" :visible.sync="dialog.visible" width="800px" append-to-body>
|
||||
<basic-form :model="configs" ref="addData">
|
||||
<div :span="24" v-for="(cell, index) in configs.networkCardConfigs" :key="index" class="m-b">
|
||||
<basic-form-item label="IP池:" validate="required" :prop="`networkCardConfigs.${index}.ipPoolId`">
|
||||
<el-select class="w" v-model="cell.ipPoolId" @change="getIp(cell)" placeholder="请选择 IP 池">
|
||||
<el-option :label="item.ipPoolName" :value="item.ipPoolId" v-for="(item, index) in ippools" :key="index"></el-option>
|
||||
</el-select>
|
||||
<el-radio-group class="m-l-md" v-model="cell.ipPolicy" size="small" @change="getIp(cell)">
|
||||
<el-radio-button label="Auto">自动</el-radio-button>
|
||||
<el-radio-button label="Manual">手动</el-radio-button>
|
||||
</el-radio-group>
|
||||
<template v-if="cell.ipPolicy && cell.ipPolicy === 'Manual'">
|
||||
<el-select v-loading="cell.loading" placeholder="请输入IP进行搜索" filterable class="m-l-md w" v-model="cell.address" size="small" multiple @visible-change="(flag) => conditionIp(flag, cell)" :filter-method="(query) => ipFilter(query, cell)" @change="selectIpChange(cell)">
|
||||
<el-option v-for="item in cell.showIps" :key="item" :label="item" :value="item" :disabled="calcAddress(cell, item)"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<el-button type="text" class="m-l-md" @click="addCard">新增</el-button>
|
||||
<el-button type="text" class="m-l-md" v-if="configs.networkCardConfigs.length > 1" @click="configs.networkCardConfigs.splice(index, 1)">删除</el-button>
|
||||
</basic-form-item>
|
||||
<div v-if="subLocation.vendorType === 'CECSTACK'" class="m-t">
|
||||
<basic-form-item label="所属网络:" validate="required" :prop="`networkCardConfigs.${index}.subnetUuid`">
|
||||
<el-select class="w m-r" v-model="cell.vpcUuid" @change="changeVPC(cell)" placeholder="请选择VPC">
|
||||
<el-option v-for="item in cell.vpcList" :label="`${item.name}(${item.vpcId})`" :value="item.vpcId" :key="item.vpcId"></el-option>
|
||||
</el-select>
|
||||
<el-select class="w" v-model="cell.subnetUuid" placeholder="请选择子网">
|
||||
<el-option v-for="item in cell.subnetList" :label="`${item.name}(${item.value})`" :value="item.value" :key="item.value"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="安全组:" validate="required" :prop="`networkCardConfigs.${index}.groups`">
|
||||
<el-select class="w" v-model="cell.groups" multiple placeholder="请选择安全组">
|
||||
<el-option v-for="item in cell.groupList" :label="`${item.name}(${item.groupUuid})`" :value="item.groupUuid" :key="item.groupUuid"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</basic-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click.native="dialog.visible = false">取消</el-button>
|
||||
<el-button type="primary" @click.native="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { conditionIp, getSubnet, getVpc, getGroup } from 'services/platform/index'
|
||||
import { cloneDeep, uniq } from 'lodash-es'
|
||||
export const CECSTACK_DEFAULT_CONFIG = {
|
||||
vpcList: [],
|
||||
subnetList: [],
|
||||
groupList: [],
|
||||
vpcUuid: '',
|
||||
networkType: 'Geneve',
|
||||
subnetUuid: '',
|
||||
ipAddress: '',
|
||||
groups: []
|
||||
}
|
||||
export const CLOUDTOWER_DEFAULT_CONFIG = {
|
||||
operation: 'newNet',
|
||||
mac: '',
|
||||
model: 'VIRTIO',
|
||||
netmask: '',
|
||||
link: 'up'
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
dialog: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
itemData: {
|
||||
type: [Object, Boolean]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ippools: [],
|
||||
prefixNodeIps: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
subLocation() {
|
||||
return this.dialog.row.subLocation
|
||||
},
|
||||
configs() {
|
||||
return this.dialog.row.configs
|
||||
},
|
||||
count() {
|
||||
const {
|
||||
nodes = 1,
|
||||
emption: { count }
|
||||
} = this.dialog.row
|
||||
return nodes * count
|
||||
},
|
||||
selectedIps() {
|
||||
const ips = []
|
||||
this.configs.networkCardConfigs.map(({ ipPolicy, address }) => {
|
||||
if (ipPolicy == 'Manual') {
|
||||
ips.push(...address)
|
||||
}
|
||||
})
|
||||
return ips
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getIpPool()
|
||||
this.configs.networkCardConfigs.map((item) => {
|
||||
if (item.address.length) {
|
||||
if (this.prefixNodeIps[item.ipPoolId]) {
|
||||
this.prefixNodeIps[item.ipPoolId].push(...item.address)
|
||||
} else {
|
||||
this.prefixNodeIps[item.ipPoolId] = [...item.address]
|
||||
}
|
||||
}
|
||||
this.getIp(item, !!this.itemData)
|
||||
this.getVpcList(item, !!this.itemData)
|
||||
this.getSgroupList(item, !!this.itemData)
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
count() {
|
||||
this.configs.networkCardConfigs.map((item) => {
|
||||
this.getIp(item)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addCard() {
|
||||
if (this.configs.networkCardConfigs.length === 3) return this.$message.warning('最多添加3张网卡')
|
||||
const first = this.configs.networkCardConfigs[0]
|
||||
let config = {}
|
||||
switch (this.subLocation.vendorType) {
|
||||
case 'CECSTACK':
|
||||
config = { ...CECSTACK_DEFAULT_CONFIG, vpcList: first.vpcList, subnetList: first.subnetList, groupList: first.groupList }
|
||||
break
|
||||
case 'CLOUDTOWER':
|
||||
config = CLOUDTOWER_DEFAULT_CONFIG
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
this.configs.networkCardConfigs.push({
|
||||
networkCardId: first.networkCardId,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false,
|
||||
...config
|
||||
})
|
||||
},
|
||||
conditionIp(flag, cell = {}) {
|
||||
// 下拉框出现
|
||||
if (flag) return
|
||||
const { address = [], ipPoolId } = cell
|
||||
if (!address.length) return
|
||||
// 上一个审批节点时传输的,就已经是锁定状态了
|
||||
// 以 addresses 为基础, 过滤不在 prefixNodeIps 中的 ip
|
||||
const filterAddress = address.filter((ip) => !this.prefixNodeIps[cell.ipPoolId]?.includes(ip))
|
||||
if (!filterAddress.length) return
|
||||
conditionIp({
|
||||
condition: JSON.stringify({
|
||||
condition: 'checkIp',
|
||||
poolId: ipPoolId,
|
||||
ips: filterAddress
|
||||
})
|
||||
}).then((data) => {
|
||||
if (!data.success) {
|
||||
this.$message.success(data.message)
|
||||
cell.address = []
|
||||
}
|
||||
})
|
||||
},
|
||||
selectIpChange(cell) {
|
||||
if (!cell.address.length) cell.showIps = this.ippools.find(({ ipPoolId }) => ipPoolId === cell.ipPoolId).freezeAllIps.slice(0, 20)
|
||||
},
|
||||
calcAddress(cell, ip) {
|
||||
// 其他虚机中是否已经选择过 禁用
|
||||
let flag = false
|
||||
if (this.selectedIps.includes(ip)) flag = true
|
||||
// 超出数量禁用
|
||||
if (cell.address.length >= this.count) flag = true
|
||||
return flag
|
||||
},
|
||||
ipFilter(query = '', cell) {
|
||||
const freezeAllIps = this.ippools.find(({ ipPoolId }) => ipPoolId === cell.ipPoolId)?.freezeAllIps || []
|
||||
if (!query) return freezeAllIps.slice(0, 20)
|
||||
// 上个节点已选
|
||||
const preIps = this.prefixNodeIps[cell.ipPoolId]?.filter((ip) => ip.includes(query)) || []
|
||||
// 已过滤
|
||||
const arr = freezeAllIps.filter((ip) => ip.includes(query)).slice(0, 20)
|
||||
// 合并并去重
|
||||
this.$set(cell, 'showIps', uniq([...preIps, ...cell.address, ...arr]))
|
||||
return cell.showIps
|
||||
},
|
||||
getVpcList(cell, isInit = false) {
|
||||
if (this.disabled || this.subLocation.vendorType !== 'CECSTACK' || !this.subLocation.vendorId || !this.subLocation.region) return
|
||||
getVpc({
|
||||
simple: true,
|
||||
params: this.$tools.handleSearchParam({
|
||||
vendorId: this.subLocation.vendorId,
|
||||
regionId: this.subLocation.region
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
cell.vpcList = data.data.rows
|
||||
if (cell.vpcUuid) this.getSubnet(cell)
|
||||
}
|
||||
})
|
||||
},
|
||||
getSgroupList(cell, isInit = false) {
|
||||
if (this.disabled || this.subLocation.vendorType !== 'CECSTACK' || !this.subLocation.vendorId || !this.subLocation.region) return
|
||||
getGroup({
|
||||
page: 1,
|
||||
rows: 9999,
|
||||
params: this.$tools.handleSearchParam({
|
||||
vendorId: this.subLocation.vendorId,
|
||||
regionId: this.subLocation.region
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
cell.groupList = data.data.rows
|
||||
}
|
||||
})
|
||||
},
|
||||
changeVPC(cell) {
|
||||
cell.subnetUuid = ''
|
||||
this.getSubnet(cell)
|
||||
},
|
||||
// 获取子网
|
||||
getSubnet(cell) {
|
||||
const vpcUuid = cell.vpcUuid
|
||||
getSubnet({
|
||||
simple: true,
|
||||
params: JSON.stringify([
|
||||
{
|
||||
param: {
|
||||
vpcUuid,
|
||||
zone: this.subLocation.az,
|
||||
vendorId: this.subLocation.vendorId
|
||||
},
|
||||
sign: 'EQ'
|
||||
}
|
||||
])
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
cell.vpcList.map((i) => {
|
||||
cell.subnetList = data.data.rows
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getIp(obj, isInit = false) {
|
||||
if (this.disabled) return
|
||||
const findIppool = this.ippools.find(({ ipPoolId }) => ipPoolId === obj.ipPoolId) || {}
|
||||
obj.ipPoolName = findIppool.ipPoolName
|
||||
obj.portGroupId = findIppool.portGroupId || ''
|
||||
if (obj.ipPolicy != 'Manual' || !obj.ipPoolId) {
|
||||
obj.address = []
|
||||
return
|
||||
}
|
||||
this.$set(obj, 'loading', true)
|
||||
conditionIp({ condition: JSON.stringify({ condition: 'listByPoolAndStatus', poolId: obj.ipPoolId, status: 'free' }) })
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
// 空闲的所有 ip
|
||||
// Vue 禁止响应式
|
||||
const freezeAllIps = Object.freeze(uniq([...(this.prefixNodeIps[obj.ipPoolId] || []), ...data.data.map(({ ip }) => ip)]))
|
||||
// 上一节点的虽然锁定,但是也是可选
|
||||
this.$set(findIppool, 'freezeAllIps', freezeAllIps)
|
||||
// 校验数量
|
||||
if (freezeAllIps.length) {
|
||||
const address = []
|
||||
freezeAllIps.forEach((ip) => {
|
||||
if (address.length === this.count) return
|
||||
if (isInit) {
|
||||
// 审批回显初始化
|
||||
address.push(...obj.address)
|
||||
} else if (obj.address.includes(ip) || !this.selectedIps.includes(ip)) {
|
||||
// 原本就在里面 || 没被使用过
|
||||
address.push(ip)
|
||||
}
|
||||
})
|
||||
obj.address = address
|
||||
this.$set(obj, 'showIps', freezeAllIps.slice(0, 20))
|
||||
} else {
|
||||
this.$message.warning('可用 IP 数量不足')
|
||||
obj.address = []
|
||||
obj.ipPoolId = ''
|
||||
obj.portGroupId = ''
|
||||
obj.ipPolicy = 'Manual'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.warning('获取 IP 失败')
|
||||
obj.address = []
|
||||
obj.ipPolicy = 'Auto'
|
||||
})
|
||||
.finally(() => (obj.loading = false))
|
||||
},
|
||||
getIpPool(manual) {
|
||||
if (manual) {
|
||||
// 切换资源池回显
|
||||
this.configs.networkCardConfigs.map((item) => {
|
||||
item.ipPoolId = ''
|
||||
})
|
||||
}
|
||||
this.ippools = []
|
||||
this.dialog.row.networkRelations.forEach((item) => {
|
||||
if (item.version == 'V4') this.ippools.push(cloneDeep(item))
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
this.$refs.addData.validate((valid) => {
|
||||
if (valid) {
|
||||
if (this.configs.networkCardConfigs.some((item) => item.loading)) return this.$message.warning('请等待 IP 获取完成')
|
||||
if (this.configs.networkCardConfigs.some((item) => item.ipPolicy === 'Manual' && !item.address?.length)) return this.$message.warning('手动模式请选择 IP')
|
||||
if (typeof this.dialog.cb === 'function') this.dialog.cb()
|
||||
this.dialog.visible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,763 @@
|
|||
<template>
|
||||
<basic-form :model="formData" ref="addForm" label-width="0" :disabled="disabled">
|
||||
<basic-table :data="showParamList" ref="addTable">
|
||||
<el-table-column label="序号" show-overflow-tooltip width="60px">
|
||||
<template slot-scope="{ $index }">
|
||||
<basic-form-item label="">
|
||||
{{ $index + 1 }}
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="命名规则" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" :prop="`showParamList.${$index}.configs.nameRuleId`">
|
||||
<el-select v-model="row.configs.nameRuleId" placeholder="请选择命名规则" clearable filterable>
|
||||
<el-option v-for="(item, index) in nameruleList" :key="index" :label="item.name" :value="item.id"> </el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="主机名" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required,vmHostName" :prop="`showParamList.${$index}.configs.vmHostName`">
|
||||
<el-input v-model="row.configs.vmHostName"> </el-input>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="云平台类型" show-overflow-tooltip>
|
||||
<template slot-scope="{ row,$index }">
|
||||
<basic-form-item label="">
|
||||
<el-select v-model="row.subLocation.vendorType">
|
||||
<el-option :label="item | vendorName" :value="item" v-for="item in vendorTypelist" :key="item"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
<el-table-column label="区域" show-overflow-tooltip width="180px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.subLocation.region`">
|
||||
<el-select v-model="row.subLocation.region" @change="changeRegion(row, true)">
|
||||
<el-option :label="item.regionName" :value="item.region" v-for="item in regionList" :key="item.region"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可用区" show-overflow-tooltip width="180px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.subLocation.az`">
|
||||
<el-select v-model="row.subLocation.az" @change="changeAz(row, true)">
|
||||
<el-option :label="item.azName" :value="item.availablitiyZone" v-for="item in getZoneListByRegion(row)" :key="item.availablitiyZone"></el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="镜像类型" show-overflow-tooltip width="180px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.osCategory`">
|
||||
<el-select v-model="row.configs.osCategory" placeholder="请选择" @change="getVersionList(row, true)">
|
||||
<el-option v-for="(item, index) in row.osList" :key="index" :label="item" :value="item"> </el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="镜像版本" show-overflow-tooltip width="180px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.osVersion`">
|
||||
<el-select v-model="row.configs.osVersion" placeholder="请选择" @change="getImage(row, true)">
|
||||
<el-option v-for="(item, index) in row.versionList" :key="index" :label="item" :value="item"> </el-option>
|
||||
</el-select>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="CPU(核)" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.cpu`">
|
||||
<el-input-number :min="1" :max="500" v-model="row.configs.cpu" @change="(val) => changeCpu(val, row)"> </el-input-number>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="内存(GB)" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.memory`">
|
||||
<el-input-number :min="row.configs.templateRam" :max="500" v-model="row.configs.memory" @change="(val) => changeMemory(val, row)"> </el-input-number>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="磁盘(GB)" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.sysDisk.disk`">
|
||||
<span class="detail-href" @click="handleOpenDiskDialog(row, $index)">
|
||||
<template v-if="getDiskTotalSize(row) > 0">{{ getDiskTotalSize(row) }}</template>
|
||||
<template v-else>配置</template>
|
||||
</span>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="网络" show-overflow-tooltip width="180px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" :prop="`showParamList.${$index}`" :rules="rules.networkCardConfigsRule">
|
||||
<span class="detail-href" @click="handleOpenNetworkDialog(row, $index)">
|
||||
<template v-if="!row.configs.networkCardConfigs.length || row.configs.networkCardConfigs[0].ipPoolId">
|
||||
<div v-for="(item, index) in row.configs.networkCardConfigs" :key="index">
|
||||
{{ item.ipPoolName }} <span v-if="item.address.length">-{{ item.address.join('、') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>配置</template>
|
||||
</span>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="密码" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required,vmPassword" :prop="`showParamList.${$index}.configs.password`">
|
||||
<el-input v-model="row.configs.password" placeholder="请输入密码" show-password></el-input>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.configs.confirm_password`">
|
||||
<el-input v-model="row.configs.confirm_password" placeholder="请再次输入密码" show-password></el-input>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.subLocation.purpose`">
|
||||
<el-input v-model="row.subLocation.purpose"> </el-input>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="软件配置" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="">
|
||||
<span class="detail-href" @click="openGraph(row, $index)"> {{ getGraphConfigLabel(row) }} </span>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" show-overflow-tooltip width="160px">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="" validate="required" :prop="`showParamList.${$index}.emption.count`">
|
||||
<el-input-number v-model="row.emption.count"> </el-input-number>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160px" fixed="right">
|
||||
<template slot-scope="{ row, $index }">
|
||||
<basic-form-item label="">
|
||||
<el-button type="text" @click="handleClone(row)">复制</el-button>
|
||||
<el-button type="text" @click="handleSub($index)">删除</el-button>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<span slot="pagination"></span>
|
||||
</basic-table>
|
||||
<DataDisk v-if="addDiskDialog.visible" :dialog="addDiskDialog" @success="changeDisk"></DataDisk>
|
||||
<Ippool v-if="addNetworkDialog.visible" :dialog="addNetworkDialog" :disabled="disabled"></Ippool>
|
||||
<el-dialog :visible.sync="graphDialog.visible" title="作业编排" width="1200px" append-to-body top="5vh" :closeOnClickModal="false">
|
||||
<Graph v-if="graphDialog.visible" :addData="addData" :graphDialog="graphDialog" @back="graphDialog.visible = false" :disabled="disabled"></Graph>
|
||||
</el-dialog>
|
||||
</basic-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { conditionService, conditionImage, getNameRule } from 'services/platform/index'
|
||||
import { generateSpec, getSpecValue } from 'views/resource-apply/utils/index'
|
||||
import { getPoolCondition } from 'services/platform/pool'
|
||||
import { uniqBy, cloneDeep } from 'lodash-es'
|
||||
import { transformNetworkConfig } from './ImageItem'
|
||||
import { subApplicationParam, GEN_UUID, ServiceCodeMap, defaultTask } from '../data/EFCInit'
|
||||
import Ippool, { CECSTACK_DEFAULT_CONFIG, CLOUDTOWER_DEFAULT_CONFIG } from './EFCIppool'
|
||||
import DataDisk from './EFCDataDisk'
|
||||
import sku from '../mixins/EFCsku'
|
||||
import Graph from '../graph/graph.vue'
|
||||
import { getVolumeTpl } from 'services/platform/smart.js'
|
||||
export default {
|
||||
name: 'VmList',
|
||||
mixins: [sku],
|
||||
components: {
|
||||
Ippool,
|
||||
DataDisk,
|
||||
Graph
|
||||
},
|
||||
props: {
|
||||
itemData: {
|
||||
type: [Object, Boolean]
|
||||
},
|
||||
showParamList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
addData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => {}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
// 使用函数的形式,可以访问到 `this`
|
||||
return {
|
||||
getSubApplicationParamsForTaskServer: () => this.addData.subApplicationParams.filter((item) => item.taskGroupUuid === this.graphDialog.task.taskGroupUuid)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formData() {
|
||||
return { showParamList: this.showParamList }
|
||||
},
|
||||
regionList() {
|
||||
return this.allPoolList.reduce((pre, cur) => {
|
||||
if (!pre.find((item) => item.region == cur.region)) {
|
||||
pre.push({
|
||||
regionName: cur.regionName,
|
||||
region: cur.region
|
||||
})
|
||||
}
|
||||
return pre
|
||||
}, [])
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nameruleList: [],
|
||||
vendorTypelist: [],
|
||||
allPoolList: [],
|
||||
addDiskDialog: {
|
||||
visible: false,
|
||||
index: '',
|
||||
row: {}
|
||||
},
|
||||
addNetworkDialog: {
|
||||
visible: false,
|
||||
row: {},
|
||||
cb: () => {}
|
||||
},
|
||||
rules: {
|
||||
networkCardConfigsRule: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value, callback) => {
|
||||
const validateNetworkCard = this.validateNetworkCard(value)
|
||||
if (validateNetworkCard.valid) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error(validateNetworkCard.message))
|
||||
}
|
||||
},
|
||||
trigger: null
|
||||
}
|
||||
]
|
||||
},
|
||||
graphDialog: {
|
||||
visible: false,
|
||||
task: {},
|
||||
cb: () => {}
|
||||
},
|
||||
CTDataDisktemplateData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
getNameRule({
|
||||
page: 1,
|
||||
rows: 9999
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
this.nameruleList = data.data.rows
|
||||
}
|
||||
})
|
||||
this.getAllPool()
|
||||
// 获取云平台类型
|
||||
conditionService('server').then((data) => {
|
||||
if (data.success) {
|
||||
this.vendorTypelist = data.data
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
getGraphConfigLabel(row) {
|
||||
const task = this.addData.tasks.find(({ taskGroupUuid }) => taskGroupUuid === row.taskGroupUuid)
|
||||
if (!task) return '配置'
|
||||
return `${task.taskName}`
|
||||
},
|
||||
openGraph(row) {
|
||||
if (!this.addData.location.name) return this.$message.error('请填写业务信息')
|
||||
if (!row.configs.vmHostName) return this.$message.error('请填写主机名')
|
||||
if (!row.configs.password) return this.$message.error('请填写密码')
|
||||
this.$emit('handleUpdateParamList')
|
||||
this.graphDialog.visible = true
|
||||
this.graphDialog.task = cloneDeep(
|
||||
this.addData.tasks.find((item) => item.taskGroupUuid === row.taskGroupUuid) || {
|
||||
...defaultTask,
|
||||
taskGroupUuid: row.taskGroupUuid
|
||||
}
|
||||
)
|
||||
this.graphDialog.cb = () => {
|
||||
this.graphDialog.task.taskName = `${this.addData.location.name}_${this.graphDialog.task.name}`
|
||||
const findIndex = this.addData.tasks.findIndex((item) => item.taskGroupUuid === row.taskGroupUuid)
|
||||
if (findIndex > -1) {
|
||||
this.addData.tasks[findIndex] = this.graphDialog.task
|
||||
} else {
|
||||
this.addData.tasks.push(this.graphDialog.task)
|
||||
}
|
||||
}
|
||||
},
|
||||
changeCpu(val, row) {
|
||||
row.elements[0].specs[0].cpu = val
|
||||
},
|
||||
changeMemory(val, row) {
|
||||
row.elements[0].specs[1].memory = val
|
||||
},
|
||||
// 将磁盘配置保存到 elements 中
|
||||
async changeDisk(index = -1, row) {
|
||||
row = index > -1 ? this.showParamList[index] : row
|
||||
// 系统盘
|
||||
row.elements[1].specs = [{ disk: row.configs.sysDisk.disk }]
|
||||
row.configs.sysDisk.categoryId = row.elements[1].categoryId
|
||||
// 数据盘
|
||||
row.elements.splice(2)
|
||||
if (row.configs.addDiskList.length) {
|
||||
const dataDiskElementClone = cloneDeep(row.elements[1])
|
||||
row.configs.addDiskList.map((item) => {
|
||||
row.elements.push({ ...dataDiskElementClone, specs: [{ disk: item.disk }] })
|
||||
})
|
||||
}
|
||||
// 设置各个云平台的默认磁盘配置
|
||||
const vendorType = row.subLocation.vendorType
|
||||
const vendorId = row.subLocation.vendorId
|
||||
switch (vendorType) {
|
||||
case 'VMWARE':
|
||||
row.configs.sysDisk.diskType = 'thin'
|
||||
// 设置磁盘默认数据
|
||||
row.configs.addDiskList.forEach((item) => {
|
||||
item.createLvm = false
|
||||
item.diskType = 'thin'
|
||||
item.fileSystem = 'ext3'
|
||||
item.forceMount = false
|
||||
})
|
||||
break
|
||||
case 'INSPURRAIL':
|
||||
row.configs.sysDisk.diskType = 'thin'
|
||||
// 设置磁盘默认数据
|
||||
row.configs.addDiskList.forEach((item) => {
|
||||
item.createLvm = false
|
||||
item.diskType = 'thin'
|
||||
item.fileSystem = 'ext3'
|
||||
item.forceMount = false
|
||||
})
|
||||
break
|
||||
case 'CLOUDTOWER':
|
||||
const busData = [
|
||||
{ name: 'VIRTIO', value: 'VIRTIO' },
|
||||
{ name: 'SCSI', value: 'SCSI' },
|
||||
{ name: 'IDE', value: 'IDE' }
|
||||
]
|
||||
// 设置磁盘默认数据
|
||||
if (!this.CTDataDisktemplateData.length || this.CTDataDisktemplateData[0].vendorId !== vendorId) {
|
||||
await getVolumeTpl({
|
||||
simple: true,
|
||||
params: this.$tools.handleSearchParam({ vendorId })
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
if (!data.data.rows.length) {
|
||||
row.configs.addDiskList = []
|
||||
return this.$message.error('获取CLOUDTOWER存储策略数据为空,请联系管理员')
|
||||
}
|
||||
this.CTDataDisktemplateData = data.data.rows
|
||||
}
|
||||
})
|
||||
}
|
||||
row.configs.addDiskList.forEach((item) => {
|
||||
item.type = 'newDisk'
|
||||
item.bus = busData[0].value
|
||||
item.volumeTemplateId = this.CTDataDisktemplateData[0].id // 存储策略
|
||||
item.volumeTemplateName = this.CTDataDisktemplateData[0].name
|
||||
})
|
||||
break
|
||||
case 'CECSTACK':
|
||||
row.configs.addDiskList.forEach((item) => {
|
||||
item.deleteWithInstance = true
|
||||
item.bootVolume = false
|
||||
// item.size = ''
|
||||
// item.volumeType = ''
|
||||
item.volumeUnit = 'GiB'
|
||||
// item.bootIndex = ''
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
handleClone(row = subApplicationParam) {
|
||||
const cloneData = cloneDeep(row)
|
||||
cloneData.taskGroupUuid = GEN_UUID()
|
||||
cloneData.taskTargetUuid = GEN_UUID()
|
||||
cloneData.configs.name = ''
|
||||
this.showParamList.push(cloneData)
|
||||
},
|
||||
handleSub(index) {
|
||||
this.showParamList.splice(index, 1)
|
||||
},
|
||||
handleOpenNetworkDialog(row, index) {
|
||||
this.addNetworkDialog.visible = true
|
||||
this.addNetworkDialog.row = row
|
||||
this.addNetworkDialog.cb = () => {
|
||||
this.$refs.addForm && this.$refs.addForm.$refs.formRef.validateField(`showParamList.${index}`)
|
||||
}
|
||||
},
|
||||
getDiskTotalSize(row) {
|
||||
return row.configs.sysDisk.disk + row.configs.addDiskList.reduce((pre, cur) => pre + cur.disk, 0)
|
||||
},
|
||||
handleOpenDiskDialog(row, index) {
|
||||
this.addDiskDialog.visible = true
|
||||
this.addDiskDialog.row = row
|
||||
this.addDiskDialog.index = index
|
||||
},
|
||||
getOsList(row) {
|
||||
if (!row.subLocation.vendorId) return
|
||||
let params
|
||||
if (row.subLocation.vendorType === 'CECSTACK') {
|
||||
params = {
|
||||
condition: 'listByImageType',
|
||||
vendorId: row.subLocation.vendorId,
|
||||
status: 'ACTIVE',
|
||||
tenantId: 0,
|
||||
regionId: row.subLocation.region,
|
||||
imageType: 'PUBLIC'
|
||||
}
|
||||
} else {
|
||||
params = {
|
||||
condition: 'listTenantImages',
|
||||
vendorId: row.subLocation.vendorId
|
||||
}
|
||||
if (row.subLocation.vendorType === 'SANGFOR') {
|
||||
if (!row.subLocation.azUuid) return
|
||||
params.azUuid = row.subLocation.azUuid
|
||||
}
|
||||
}
|
||||
conditionImage(params).then((data) => {
|
||||
if (data.success) {
|
||||
row.imageData = data.data
|
||||
this.$set(row, 'osList', Object.keys(row.imageData))
|
||||
if (!this.itemData) {
|
||||
row.configs.osCategory = row.osList[0]
|
||||
row.configs.osVersion = ''
|
||||
}
|
||||
this.$set(row, 'versionList', [])
|
||||
this.getVersionList(row, !row.configs.osVersion)
|
||||
}
|
||||
})
|
||||
return row.osList
|
||||
},
|
||||
|
||||
getVersionList(row, flag) {
|
||||
if (row.subLocation.vendorType === 'CECSTACK') {
|
||||
row.versionList = row.imageData[row.configs.osCategory].map(({ name }) => name)
|
||||
} else {
|
||||
row.versionList = Object.keys(row.imageData[row.configs.osCategory])
|
||||
}
|
||||
if (flag) {
|
||||
row.configs.osVersion = row.versionList[0]
|
||||
this.getImage(row, true)
|
||||
} else {
|
||||
this.getImage(row, false)
|
||||
}
|
||||
},
|
||||
getImage(row, flag) {
|
||||
let obj
|
||||
if (row.subLocation.vendorType === 'CECSTACK') {
|
||||
obj = row.imageData[row.configs.osCategory].find(({ name }) => row.configs.osVersion === name)
|
||||
} else {
|
||||
obj = row.imageData[row.configs.osCategory][row.configs.osVersion]?.[0]
|
||||
}
|
||||
if (!obj) return
|
||||
if (flag) {
|
||||
row.configs.imageId = obj.id
|
||||
// 电子云为 minDisk
|
||||
// cloudtower 镜像中可能没系统盘大小
|
||||
row.configs.templateDisk = obj.templateDisk || obj.minDisk || 50
|
||||
row.configs.templateRam = obj.minRam / 1024 || 1
|
||||
if (row.configs.memory < row.configs.templateRam) row.configs.memory = row.configs.templateRam
|
||||
row.configs.sysDisk.disk = row.configs.templateDisk
|
||||
}
|
||||
if (row.subLocation.vendorType == 'CECSTACK') {
|
||||
if (flag) {
|
||||
this.$set(row.configs, 'networkCardConfigs', [
|
||||
{
|
||||
networkId: obj.networkId,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false,
|
||||
// 电子云特殊参数
|
||||
...CECSTACK_DEFAULT_CONFIG
|
||||
}
|
||||
])
|
||||
}
|
||||
} else if (row.subLocation.vendorType == 'SANGFOR') {
|
||||
row.configs.vmUuid = obj.imageUuid
|
||||
if (flag && row.configs.networkCardConfigs.length == 0) {
|
||||
const hardware_status = JSON.parse(obj.detail || '{}')?.hardware_status
|
||||
if (!hardware_status) return this.$message.error('镜像缺少网卡,请检查镜像是否正确')
|
||||
const configs = transformNetworkConfig(hardware_status).map((item) => ({
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false
|
||||
}))
|
||||
this.$set(row.configs, 'networkCardConfigs', configs)
|
||||
}
|
||||
} else if (row.subLocation.vendorType == 'VMWARE') {
|
||||
if (flag && (row.configs.networkCardConfigs.length == 0 || row.configs.networkCardConfigs[0].networkCardId != obj.networkCards?.[0]?.id)) {
|
||||
if (!obj.networkCards?.length) this.$message.error('镜像缺少网卡,请检查镜像是否正确')
|
||||
this.$set(
|
||||
row.configs,
|
||||
'networkCardConfigs',
|
||||
obj.networkCards.map((item) => {
|
||||
return {
|
||||
networkCardId: item.id,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} else if (row.subLocation.vendorType == 'INSPURRAIL') {
|
||||
if (flag) {
|
||||
if (row.configs.networkCardConfigs.length == 0 || row.configs.networkCardConfigs[0].networkCardId != obj.networkCards?.[0]?.id) {
|
||||
if (!obj.networkCards?.length) this.$message.error('镜像缺少网卡,请检查镜像是否正确')
|
||||
this.$set(
|
||||
row.configs,
|
||||
'networkCardConfigs',
|
||||
obj.networkCards.map((item) => {
|
||||
return {
|
||||
networkCardId: item.id,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
// 模板里面的磁盘配置
|
||||
const volumes = obj.volumes || []
|
||||
volumes.map(({ bootVolume, size }) => {
|
||||
if (bootVolume) {
|
||||
row.configs.templateDisk = size
|
||||
row.configs.sysDisk.disk = size
|
||||
} else {
|
||||
row.configs.addDiskList.push({ disk: size, templateDisk: size })
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (row.subLocation.vendorType == 'CLOUDTOWER') {
|
||||
if (flag) {
|
||||
this.$set(row.configs, 'networkCardConfigs', [
|
||||
{
|
||||
networkId: obj.networkId,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
ipPoolName: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
loading: false,
|
||||
// CLOUDTOWER特殊参数
|
||||
...CLOUDTOWER_DEFAULT_CONFIG
|
||||
}
|
||||
])
|
||||
}
|
||||
} else if (row.subLocation.vendorType === 'CNWARE') {
|
||||
const res = JSON.parse(obj.configuration).devices.map((d) => {
|
||||
if (d.capacity) {
|
||||
const size = Number(d.capacity) / 1024 / 1024 / 1024
|
||||
// 系统盘大小
|
||||
row.configs.templateDisk = size
|
||||
}
|
||||
return {
|
||||
...d
|
||||
}
|
||||
})
|
||||
if (flag) {
|
||||
row.configs.sysDiskList = res.flat()
|
||||
}
|
||||
}
|
||||
// 设置硬盘参数
|
||||
if (flag) this.changeDisk(-1, row)
|
||||
},
|
||||
setVendor(row, manual = true) {
|
||||
const findVendorList = this.getVendorListByRegionZone(row)
|
||||
const pool = findVendorList.filter((item) => item.vendorId == row.subLocation.vendorId)[0]
|
||||
if (!pool) return this.$message.error('当前可用区下不存在资源池')
|
||||
console.log('当前资源池:', pool.vendorType, pool.name)
|
||||
const { vendorType, id, azUuid } = pool
|
||||
row.subLocation.poolGroupId = id
|
||||
row.subLocation.azUuid = azUuid
|
||||
row.subLocation.vendorType = vendorType
|
||||
row.networkRelations = pool.networkRelations || []
|
||||
row.service = ServiceCodeMap.server[vendorType]
|
||||
row.elements.map((item, index) => {
|
||||
item.serviceCode = index === 0 ? ServiceCodeMap.server[vendorType] : ServiceCodeMap.disk[vendorType]
|
||||
})
|
||||
switch (vendorType) {
|
||||
case 'VMWARE':
|
||||
row.configs.isAddShterm = false
|
||||
break
|
||||
case 'INSPURRAIL':
|
||||
row.configs.isAddShterm = false
|
||||
break
|
||||
case 'CLOUDTOWER':
|
||||
row.configs.serverId = 0
|
||||
row.configs.ha = true
|
||||
row.configs.firmware = 'BIOS'
|
||||
row.configs.status = 'running'
|
||||
row.configs.fullClone = false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
// 根据云平台类型设置默认磁盘配置
|
||||
this.changeDisk(-1, row)
|
||||
this.getData(row, manual)
|
||||
},
|
||||
getData(row, manual = true) {
|
||||
// 加载服务目录
|
||||
if (!this.itemData || !this.disabled) this.loadSku(row)
|
||||
this.getOsList(row)
|
||||
},
|
||||
|
||||
getZoneListByRegion(row) {
|
||||
const filterPools = this.allPoolList.filter((item) => item.region == row.subLocation.region)
|
||||
if (!filterPools.length) return []
|
||||
// 同一地域下可能会存在重复可用区,去重处理
|
||||
if (filterPools[0].vendorType === 'CECSTACK') {
|
||||
const formatPools = []
|
||||
// 将多个可用区扁平化放在资源池中
|
||||
filterPools.map((pool) => {
|
||||
pool.regionRelations.map((zone) => {
|
||||
formatPools.push({ ...pool, availablitiyZone: zone.zoneId, azName: zone.zoneName })
|
||||
})
|
||||
})
|
||||
return uniqBy(formatPools, 'availablitiyZone')
|
||||
} else {
|
||||
return uniqBy(filterPools, 'availablitiyZone')
|
||||
}
|
||||
},
|
||||
getVendorListByRegionZone(row) {
|
||||
const zoneList = this.getZoneListByRegion(row)
|
||||
return zoneList.filter((item) => item.availablitiyZone == row.subLocation.az)
|
||||
},
|
||||
// 地域变化
|
||||
// manual 手动切换
|
||||
changeRegion(row, manual = true) {
|
||||
this.setZone(row, manual)
|
||||
},
|
||||
// 设置可用区
|
||||
setZone(row, manual = true) {
|
||||
const zoneList = this.getZoneListByRegion(row)
|
||||
// 手动切换才需要置空
|
||||
if (manual && !zoneList.length) return (row.subLocation.az = '')
|
||||
if (!zoneList.find((item) => item.availablitiyZone === row.subLocation.az)) {
|
||||
row.subLocation.az = zoneList[0].availablitiyZone
|
||||
}
|
||||
this.changeAz(row, manual)
|
||||
},
|
||||
// 可用区改变
|
||||
changeAz(row, manual = true) {
|
||||
const findVendorList = this.getVendorListByRegionZone(row)
|
||||
if (manual && !findVendorList.length) return (row.subLocation.vendorId = '')
|
||||
if (!findVendorList.find((item) => item.vendorId === row.subLocation.vendorId)) {
|
||||
row.subLocation.vendorId = findVendorList[0].vendorId
|
||||
}
|
||||
this.setVendor(row, manual)
|
||||
},
|
||||
// 所有资源池
|
||||
async getAllPool() {
|
||||
const res = await getPoolCondition({
|
||||
condition: 'listPoolGroups',
|
||||
tenantId: this.addData.location.tenantId
|
||||
})
|
||||
if (!res.success) return
|
||||
this.allPoolList = res.data
|
||||
if (!this.regionList.length) return
|
||||
const [{ region }] = this.regionList
|
||||
if (!this.showParamList[0].subLocation.region) {
|
||||
this.showParamList[0].subLocation.region = region
|
||||
}
|
||||
this.changeRegion(this.showParamList[0], false)
|
||||
},
|
||||
validateNetworkCard(param) {
|
||||
const res = {
|
||||
valid: true,
|
||||
message: ''
|
||||
}
|
||||
let flag = false
|
||||
let addressLenFlag = false
|
||||
let cecstackFlag = false
|
||||
param.configs.networkCardConfigs.forEach((item) => {
|
||||
if (!item.ipPoolId || (item.ipPolicy == 'Manual' && item.address.length == 0) || (item.checkIpv6 && (!item.ipv6PoolId || (item.ipv6Policy == 'Manual' && item.ipv6Address.length == 0)))) flag = true
|
||||
if (item.ipPolicy == 'Manual' && item.address.length !== param.emption.count) {
|
||||
addressLenFlag = true
|
||||
}
|
||||
if (param.subLocation.vendorType === 'CECSTACK' && !(item.vpcUuid || item.subnetUuid)) {
|
||||
cecstackFlag = true
|
||||
}
|
||||
})
|
||||
if (flag) {
|
||||
res.valid = false
|
||||
res.message = '网卡信息配置不完善'
|
||||
return res
|
||||
}
|
||||
if (addressLenFlag) {
|
||||
res.valid = false
|
||||
res.message = 'IP数量与云主机订购数量不一致'
|
||||
return res
|
||||
}
|
||||
if (cecstackFlag) {
|
||||
res.valid = false
|
||||
res.message = '缺少 VPC 和子网信息'
|
||||
}
|
||||
return res
|
||||
},
|
||||
getParams() {
|
||||
let data = false
|
||||
this.$refs.addForm.validate((valid) => {
|
||||
if (valid) {
|
||||
const result = []
|
||||
this.showParamList.forEach((param) => {
|
||||
const validateNetworkCard = this.validateNetworkCard(param)
|
||||
if (!validateNetworkCard.valid) {
|
||||
data = false
|
||||
return
|
||||
}
|
||||
const { password, confirm_password, ...other } = param.configs
|
||||
if (password !== confirm_password) {
|
||||
data = false
|
||||
this.$message.error('两次密码输入不一致')
|
||||
return
|
||||
}
|
||||
result.push({
|
||||
...other,
|
||||
categoryId: param.elements[0].categoryId
|
||||
})
|
||||
})
|
||||
if (result.length === this.showParamList.length) data = result
|
||||
} else {
|
||||
this.$message.error('虚拟机信息未填写完整')
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -13,7 +13,26 @@
|
|||
<script>
|
||||
import { getDictionaries, conditionImage } from 'services/platform/index'
|
||||
import { element } from '../data/init'
|
||||
export function transformNetworkConfig(netConfig) {
|
||||
// 创建一个空数组来存放转换后的结果
|
||||
const result = []
|
||||
|
||||
// 遍历对象的每个键值对
|
||||
for (const key in netConfig) {
|
||||
// 解析每个属性的值
|
||||
const configParts = netConfig[key].split(',')
|
||||
const configObj = {}
|
||||
// 遍历每个配置部分,将其转换为对象的键值对
|
||||
configParts.forEach((part) => {
|
||||
const [key, value] = part.split('=')
|
||||
configObj[key.trim()] = value ? value.trim() : '' // 确保即使没有值,也添加键
|
||||
})
|
||||
// 将转换后的对象添加到结果数组中
|
||||
result.push(configObj)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
addData: {
|
||||
|
@ -43,15 +62,18 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
getOsList() {
|
||||
conditionImage({
|
||||
if (!this.addData.location.vendorId) return
|
||||
const params = {
|
||||
condition: 'listTenantImages',
|
||||
vendorId: this.addData.location.vendorId
|
||||
}).then((data) => {
|
||||
}
|
||||
if (this.addData.location.vendorType === 'SANGFOR') {
|
||||
if (!this.addData.location.azUuid) return
|
||||
params.azUuid = this.addData.location.azUuid
|
||||
}
|
||||
conditionImage(params).then((data) => {
|
||||
if (data.success) {
|
||||
this.imageData = data.data
|
||||
if (this.addData.location.vendorType == 'SMARTX') {
|
||||
this.$set(this.addData.configs, 'templateDisk', data.data.templateDisk)
|
||||
}
|
||||
this.osList = Object.keys(this.imageData)
|
||||
if (!this.addData.configs.osCategory) {
|
||||
this.addData.configs.osCategory = this.osList[0]
|
||||
|
@ -74,26 +96,68 @@ export default {
|
|||
getImage(flag) {
|
||||
const obj = this.imageData[this.addData.configs.osCategory][this.addData.configs.osVersion][0]
|
||||
this.addData.configs.imageId = obj.id
|
||||
this.addData.configs.templateDisk = obj.templateDisk
|
||||
if (flag && this.addData.location.vendorType == 'VMWARE' && (this.addData.configs.networkCardConfigs.length == 0 || this.addData.configs.networkCardConfigs[0].networkCardId != obj.networkCards[0].id)) {
|
||||
this.$set(
|
||||
this.addData.configs,
|
||||
'networkCardConfigs',
|
||||
obj.networkCards.map((item) => {
|
||||
return {
|
||||
networkCardId: item.id,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
checkIpv6: false,
|
||||
ipv6PoolId: '',
|
||||
ipv6Policy: 'Auto',
|
||||
ipv6Address: []
|
||||
}
|
||||
this.addData.configs.templateDisk = obj.templateDisk || 50 // cloudtower 镜像中可能没系统盘大小
|
||||
if (this.addData.location.vendorType == 'SANGFOR') {
|
||||
this.addData.configs.vmUuid = obj.imageUuid
|
||||
if (flag && this.addData.configs.networkCardConfigs.length == 0) {
|
||||
const hardware_status = JSON.parse(obj.detail || '{}')?.hardware_status
|
||||
if (!hardware_status) return this.$message.error('镜像缺少网卡,请检查镜像是否正确')
|
||||
const configs = transformNetworkConfig(hardware_status).map((item) => ({
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
address: [],
|
||||
loading: false
|
||||
}))
|
||||
this.$set(this.addData.configs, 'networkCardConfigs', configs)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (this.addData.location.vendorType == 'VMWARE') {
|
||||
if (flag && (this.addData.configs.networkCardConfigs.length == 0 || this.addData.configs.networkCardConfigs[0].networkCardId != obj.networkCards?.[0]?.id)) {
|
||||
if (!obj.networkCards?.length) this.$message.error('镜像缺少网卡,请检查镜像是否正确')
|
||||
this.$set(
|
||||
this.addData.configs,
|
||||
'networkCardConfigs',
|
||||
obj.networkCards.map((item) => {
|
||||
return {
|
||||
networkCardId: item.id,
|
||||
ipPolicy: 'Auto',
|
||||
ipPoolId: '',
|
||||
address: [],
|
||||
portGroupId: '',
|
||||
checkIpv6: false,
|
||||
ipv6PoolId: '',
|
||||
ipv6Policy: 'Auto',
|
||||
ipv6Address: [],
|
||||
loading: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
} else if (this.addData.location.vendorType == 'CLOUDTOWER') {
|
||||
// 系统盘大小
|
||||
this.$set(this.elements, 'insAmount', this.addData.configs.templateDisk)
|
||||
if (flag) {
|
||||
this.addData.configs.networkCardConfigs.map((item) => {
|
||||
item.networkId = obj.networkId
|
||||
})
|
||||
)
|
||||
} else if (flag && this.addData.location.vendorType == 'SMARTX' && (this.addData.configs.networkCardConfigs.length == 0 || this.addData.configs.networkCardConfigs[0].networkCardId != obj.networkCards[0].id)) {
|
||||
}
|
||||
} else if (this.addData.location.vendorType === 'CNWARE') {
|
||||
const res = JSON.parse(obj.configuration).devices.map((d) => {
|
||||
if (d.capacity) {
|
||||
const size = Number(d.capacity) / 1024 / 1024 / 1024
|
||||
// 系统盘大小
|
||||
this.addData.configs.templateDisk = size
|
||||
this.$set(this.elements, 'insAmount', size)
|
||||
}
|
||||
return {
|
||||
...d
|
||||
}
|
||||
})
|
||||
if (flag) {
|
||||
this.addData.configs.sysDiskList = res.flat()
|
||||
}
|
||||
} else if (flag && this.addData.location.vendorType == 'SMARTX' && (this.addData.configs.networkCardConfigs.length == 0 || this.addData.configs.networkCardConfigs[0].networkCardId != obj.networkCards[0]?.id)) {
|
||||
const [{ id: categoryId }] = this.elements.categoryList
|
||||
this.elements.elements = obj.volumes.map((volume) => {
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<basic-form-item label="数据盘:" class="m-b-lg">
|
||||
<el-row>
|
||||
<el-col :span="24" v-for="(cell, index) in disks" :key="index" class="m-t">
|
||||
<el-col :span="24">
|
||||
<el-select filterable v-model="cell.categoryId" @change="getSku(cell)" class="w m-r-xs">
|
||||
<el-option :label="item.name" v-for="(item, index) in categoryList" :key="index" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
<el-select filterable v-model="cell.skuId" class="w m-r-xs">
|
||||
<el-option :label="generateSpec(item.spec)" v-for="item in cell.skuList" :key="item.id" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
总线:
|
||||
<el-select filterable v-model="cell.bus" class="w m-r-xs">
|
||||
<el-option v-for="(item, index) in busData" :key="index" :label="item.name" :value="item.value"></el-option>
|
||||
</el-select>
|
||||
存储策略:
|
||||
<el-select filterable v-model="cell.volumeTemplateId" @change="volumeTemplateChange(cell)" class="w m-r-xs">
|
||||
<el-option v-for="(item, index) in templateData" :key="index" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
<el-button size="mini" class="m-r-sm" type="danger" v-if="disks.length > 1" icon="el-icon-minus" @click="removeItem(index)"></el-button>
|
||||
</el-col>
|
||||
</el-col>
|
||||
<el-col :span="4" class="add-border" :class="{ 'm-t-md': disks.length }" @click.native="addItem()">
|
||||
<i class="el-icon-plus"></i>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
<script>
|
||||
import { element } from '../../data/init'
|
||||
import { getCategoriesByCode, getSkus } from 'services/services/product'
|
||||
import { formatEqParams } from 'utils'
|
||||
import { getVolumeTpl } from 'services/platform/smart.js'
|
||||
import { generateSpec } from 'views/resource-apply/utils/index'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
addData: {
|
||||
type: Object
|
||||
},
|
||||
item: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
categoryList: [],
|
||||
fileList: [],
|
||||
busData: [
|
||||
{ name: 'VIRTIO', value: 'VIRTIO' },
|
||||
{ name: 'SCSI', value: 'SCSI' },
|
||||
{ name: 'IDE', value: 'IDE' }
|
||||
],
|
||||
templateData: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
disks() {
|
||||
return this.item.elements
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getCategoryList()
|
||||
await this.getTemplateData()
|
||||
if (!this.disks.length) this.addItem()
|
||||
},
|
||||
methods: {
|
||||
generateSpec,
|
||||
volumeTemplateChange(cell) {
|
||||
cell.volumeTemplateName = this.templateData.find(({ id }) => id === cell.volumeTemplateId).name
|
||||
},
|
||||
async getTemplateData() {
|
||||
await getVolumeTpl({
|
||||
simple: true,
|
||||
params: this.$tools.handleSearchParam({
|
||||
vendorId: this.addData.location.vendorId
|
||||
})
|
||||
}).then((data) => {
|
||||
if (data.success) {
|
||||
this.templateData = data.data.rows
|
||||
}
|
||||
})
|
||||
},
|
||||
loadSku() {
|
||||
if (this.disks.length) {
|
||||
this.handleShowData()
|
||||
}
|
||||
},
|
||||
handleShowData() {
|
||||
this.disks.forEach((item) => {
|
||||
this.getSku(item)
|
||||
})
|
||||
},
|
||||
// 获取服务类型
|
||||
async getCategoryList() {
|
||||
await getCategoriesByCode('cloudtower.standard.volume').then((data) => {
|
||||
if (data.success) {
|
||||
if (!data.data.length) return this.$message.error('该服务不存在产品类型请检查')
|
||||
this.categoryList = data.data
|
||||
this.item.categoryMap = this.getCategoryMap(data.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取商品的sku
|
||||
getSku(item) {
|
||||
const { categoryId, skuId } = item
|
||||
this.loading = true
|
||||
getSkus(formatEqParams({ categoryId }))
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
if (!data.data.length) return this.$message.error('该产品类型下不存在产品,请检查')
|
||||
if (!skuId) {
|
||||
const cell = data.data[0]
|
||||
item.skuId = cell.id
|
||||
}
|
||||
this.$set(
|
||||
item,
|
||||
'skuList',
|
||||
data.data.map((item) => {
|
||||
const spec = JSON.parse(item.spec)
|
||||
return {
|
||||
...item,
|
||||
spec
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
// 生成服务类型map
|
||||
getCategoryMap(data) {
|
||||
const map = {}
|
||||
data.forEach((item) => {
|
||||
const { id, name, remark, props } = item
|
||||
map[id] = { name, remark, props }
|
||||
})
|
||||
return map
|
||||
},
|
||||
removeItem(index) {
|
||||
this.disks.splice(index, 1)
|
||||
},
|
||||
addItem() {
|
||||
if (this.disks.length === 4) return this.$message.error('数据盘最多可增加四个')
|
||||
const [{ id: categoryId }] = this.categoryList
|
||||
if (!this.templateData.length) return this.$message.error('存储策略为空,添加失败')
|
||||
const result = {
|
||||
...element,
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
categoryId,
|
||||
categoryMap: this.item.categoryMap,
|
||||
size: '',
|
||||
type: 'newDisk',
|
||||
// name: '',
|
||||
bus: 'VIRTIO', // 总线
|
||||
volumeTemplateId: this.templateData[0].id, // 存储策略
|
||||
volumeTemplateName: this.templateData[0].name
|
||||
}
|
||||
this.getSku(result)
|
||||
this.disks.push(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.w {
|
||||
width: 150px !important;
|
||||
}
|
||||
.add-border {
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
border: 1px dashed black;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,303 @@
|
|||
<template>
|
||||
<CommonWrapper code="compute" :add-data="addData" ref="common" :elements="elements" :get-params="getParams" :item-data="retention" :disabled="disabled">
|
||||
<div class="item-block">
|
||||
<h5>配置信息</h5>
|
||||
<basic-form-item label="产品类型:">
|
||||
<el-radio-group v-model="currentElement.categoryId" @change="getSku(currentElement)">
|
||||
<el-radio-button :label="item.id" v-for="(item, index) in currentElement.categoryList" :key="index">{{ item.name }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</basic-form-item>
|
||||
<basic-form-item label="规格:" validate="required">
|
||||
<sku-table :skus="currentElement.skuList" style="max-width: 800px" :mode="addData.emption.duration.mode" :show-price="true">
|
||||
<el-table-column show-overflow-tooltip label="规格代码" prop="code">
|
||||
<template v-slot="scope">
|
||||
<el-radio v-model="currentElement.skuId" :label="scope.row.id">{{ scope.row.code }}</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</sku-table>
|
||||
</basic-form-item>
|
||||
<!-- 系统盘在镜像里面设置 -->
|
||||
<ImageItem :add-data="addData" :elements="systemElement" v-if="addData.location.vendorId"></ImageItem>
|
||||
<basic-form-item label="系统盘:"> <el-input class="w m-r" disabled v-model="addData.configs.templateDisk"></el-input>GB </basic-form-item>
|
||||
<DataDisk :add-data="addData" :item="elements[2]" ref="diskRef"></DataDisk>
|
||||
</div>
|
||||
<div class="item-block">
|
||||
<h5>网络信息</h5>
|
||||
<ipPool :add-data="addData" v-if="addData.networkRelations.length" :disabled="disabled"></ipPool>
|
||||
</div>
|
||||
<div class="item-block">
|
||||
<h5>云主机信息</h5>
|
||||
<basic-form :model="vmData" ref="addForm" label-position="left" :disabled="disabled">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<basic-form-item label="云主机名称:" validate="required" prop="name" label-width="107px">
|
||||
<el-input v-model="vmData.name" placeholder="请输入云主机名" class="w-lg"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<basic-form-item label="密码设置:" validate="required" prop="password">
|
||||
<el-input v-model="vmData.password" placeholder="请输入密码" show-password class="w-lg"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<basic-form-item label="确认密码:" validate="required" prop="confirm_password">
|
||||
<el-input v-model="vmData.confirm_password" placeholder="请确认密码" show-password class="w-lg"></el-input>
|
||||
</basic-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form>
|
||||
</div>
|
||||
</CommonWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommonWrapper from '../../components/CommonWrapper.vue'
|
||||
import crypto from 'utils/crypto'
|
||||
import DataDisk from './DataDisk.vue'
|
||||
import ImageItem from './../ImageItem.vue'
|
||||
import sku from '../../mixins/sku'
|
||||
import { add, element } from '../../data/init'
|
||||
import ipPool from './ippool.vue'
|
||||
import { cloneDeep, findLastKey } from 'lodash-es'
|
||||
import { generateSpec, getSpecValue } from 'views/resource-apply/utils/index'
|
||||
import { getShoppingCartDetail } from 'services/system/shop_cart'
|
||||
// import LabelItemNew from '../../components/LabelItemNew.vue'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String
|
||||
},
|
||||
itemData: {
|
||||
type: [Object, Boolean]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
components: { CommonWrapper, DataDisk, ipPool, ImageItem },
|
||||
mixins: [sku],
|
||||
watch: {
|
||||
'addData.location.vendorType'() {
|
||||
this.$emit('type', this.addData.location.vendorType)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elements: [
|
||||
{
|
||||
...element,
|
||||
name: '云主机',
|
||||
serviceCode: 'cloudtower.standard.server',
|
||||
main: true
|
||||
},
|
||||
{
|
||||
...element,
|
||||
name: '系统盘',
|
||||
serviceCode: 'cloudtower.standard.volume'
|
||||
},
|
||||
{
|
||||
...element,
|
||||
name: '数据盘',
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
isLoadData: false,
|
||||
elements: []
|
||||
}
|
||||
],
|
||||
addData: {
|
||||
...cloneDeep(add),
|
||||
location: {
|
||||
...add.location,
|
||||
vendorType: this.type
|
||||
},
|
||||
service: 'cloudtower.standard.server',
|
||||
configs: {
|
||||
templateId: '',
|
||||
resourceLabel: [],
|
||||
diskCategoryId: 0,
|
||||
osCategory: '',
|
||||
osVersion: '',
|
||||
volumeModels: [],
|
||||
networkCardConfigs: [
|
||||
{
|
||||
operation: 'newNet',
|
||||
networkId: '',
|
||||
mac: '',
|
||||
model: 'VIRTIO',
|
||||
ipAddress: [], // 对应一个ip,可以为空
|
||||
netmask: '',
|
||||
link: 'up',
|
||||
loading: false
|
||||
}
|
||||
],
|
||||
serverId: 0,
|
||||
ha: true,
|
||||
firmware: 'BIOS',
|
||||
status: 'running',
|
||||
fullClone: false
|
||||
},
|
||||
networkRelations: []
|
||||
},
|
||||
vmData: {
|
||||
name: '',
|
||||
confirm_password: '',
|
||||
password: '',
|
||||
resourceLabel: []
|
||||
},
|
||||
retention: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentElement() {
|
||||
return this.elements[0]
|
||||
},
|
||||
systemElement() {
|
||||
return this.elements[1]
|
||||
},
|
||||
specArray() {
|
||||
const { skuList, skuId } = this.currentElement
|
||||
const item = skuList.find((item) => item.id === skuId)
|
||||
if (!item) return []
|
||||
const result = item.spec
|
||||
return result
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
if (this.itemData) {
|
||||
this.retention = this.itemData
|
||||
this.handleShowData()
|
||||
} else if (this.$route.query.id) {
|
||||
const res = await getShoppingCartDetail(this.$route.query.id)
|
||||
if (res.success) {
|
||||
this.retention = JSON.parse(res.data.inventory)
|
||||
this.handleShowData()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getPostData() {
|
||||
let data = false
|
||||
data = this.$refs.common.handlePostData()
|
||||
return data
|
||||
},
|
||||
// 数据回现展示
|
||||
handleShowData() {
|
||||
this.addData = {
|
||||
...cloneDeep(this.retention),
|
||||
networkRelations: []
|
||||
}
|
||||
const { name, password, resourceLabel } = this.addData.configs
|
||||
this.vmData = {
|
||||
name,
|
||||
password: crypto.decrypt(password),
|
||||
confirm_password: crypto.decrypt(password),
|
||||
resourceLabel
|
||||
}
|
||||
const { configs, elements } = this.retention
|
||||
const { diskCategoryId, volumeModels } = configs
|
||||
const [first, two, ...others] = elements
|
||||
this.elements = [
|
||||
{
|
||||
...element,
|
||||
...first
|
||||
},
|
||||
{
|
||||
...element,
|
||||
name: '系统盘',
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
categoryId: diskCategoryId,
|
||||
insAmount: configs.templateDisk, // configs 中系统盘大小在镜像中设置的
|
||||
...two
|
||||
},
|
||||
{
|
||||
serviceCode: 'cloudtower.standard.volume',
|
||||
isLoadData: false,
|
||||
elements: others.map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
...volumeModels[index]
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
setTimeout(() => {
|
||||
this.initLoad()
|
||||
this.$refs.diskRef.loadSku()
|
||||
})
|
||||
},
|
||||
generateSpec,
|
||||
getParams() {
|
||||
let data = false
|
||||
this.$refs.addForm.validate((valid) => {
|
||||
if (valid) {
|
||||
if (!this.elements[2].elements.length) {
|
||||
this.$message.error('至少要有一块磁盘')
|
||||
return false
|
||||
}
|
||||
if (!this.addData.configs.networkCardConfigs.length) {
|
||||
this.$message.error('至少要有一个网卡')
|
||||
return false
|
||||
}
|
||||
let flag = false
|
||||
let lengthValid = false
|
||||
this.addData.configs.networkCardConfigs.forEach((item) => {
|
||||
if (!item.ipPoolId || !item.ipAddress) flag = true
|
||||
// 申请2台虚机,那么 address 也必须选择 2 个
|
||||
if (item.ipAddress?.length !== this.addData.emption.count) lengthValid = true
|
||||
})
|
||||
if (lengthValid) {
|
||||
this.$message.error('IP数量与申请云主机数量不一致')
|
||||
return false
|
||||
}
|
||||
if (flag) {
|
||||
this.$message.error('网卡信息配置不完善')
|
||||
return false
|
||||
}
|
||||
const { name, password, confirm_password, ...other } = this.vmData
|
||||
if (password !== confirm_password) {
|
||||
this.$message.error('两次密码输入不一致')
|
||||
return false
|
||||
}
|
||||
|
||||
const result = {
|
||||
...other,
|
||||
name,
|
||||
password: crypto.encrypt(password),
|
||||
diskCategoryId: this.elements[1].categoryId,
|
||||
categoryId: this.currentElement.categoryId,
|
||||
// 系统盘
|
||||
...getSpecValue(this.systemElement)
|
||||
}
|
||||
const { elements, categoryMap } = this.elements[2]
|
||||
result.volumeModels = elements.map((item) => {
|
||||
const { categoryId, size, type, bus, volumeTemplateId, volumeTemplateName } = item
|
||||
return {
|
||||
categoryId,
|
||||
// 数据盘
|
||||
...getSpecValue(item),
|
||||
size: getSpecValue(item).disk,
|
||||
type,
|
||||
category: categoryMap ? categoryMap[categoryId].remark : '',
|
||||
bus,
|
||||
volumeTemplateId,
|
||||
volumeTemplateName
|
||||
}
|
||||
})
|
||||
this.specArray.forEach((item) => {
|
||||
result[item.specName] = item.specValue
|
||||
})
|
||||
result.skuId = this.currentElement.skuId
|
||||
data = result
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '../../index.scss';
|
||||
</style>
|
|
@ -0,0 +1,194 @@
|
|||
<template>
|
||||
<basic-form-item label="网卡:" class="m-b-lg">
|
||||
<el-row>
|
||||
<el-col :span="24" v-for="(cell, index) in configs.networkCardConfigs" :key="index" class="m-b">
|
||||
<el-select class="w" v-model="cell.ipPoolId" @change="getIp(cell)">
|
||||
<el-option :label="item.ipPoolName" :value="item.ipPoolId" v-for="(item, index) in ippools" :key="index"></el-option>
|
||||
</el-select>
|
||||
<el-select v-loading="cell.loading" placeholder="请输入IP进行搜索" filterable class="m-l-md w" v-model="cell.ipAddress" size="small" multiple @visible-change="(flag) => conditionIp(flag, cell)" :filter-method="(query) => ipFilter(query, cell)" @change="selectIpChange(cell)">
|
||||
<el-option v-for="item in cell.showIps" :key="item" :label="item" :value="item" :disabled="calcAddress(cell, item)"></el-option>
|
||||
</el-select>
|
||||
<!-- <el-button @click="addCard" icon="el-icon-plus" size="mini" type="primary"></el-button> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
</basic-form-item>
|
||||
</template>
|
||||
<script>
|
||||
import { conditionIp } from 'services/platform/index'
|
||||
import { isArray, cloneDeep, uniq } from 'lodash-es'
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
addData: {
|
||||
type: Object
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
},
|
||||
retention: {
|
||||
type: [Object, Boolean]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ippools: [],
|
||||
prefixNodeIps: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
configs() {
|
||||
return this.addData.configs
|
||||
},
|
||||
count() {
|
||||
const {
|
||||
nodes = 1,
|
||||
emption: { count }
|
||||
} = this.addData
|
||||
return nodes * count
|
||||
},
|
||||
selectedIps() {
|
||||
const ips = []
|
||||
this.addData.configs.networkCardConfigs.map(({ ipAddress }) => {
|
||||
ips.push(...ipAddress)
|
||||
})
|
||||
return ips
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getIpPool()
|
||||
this.configs.networkCardConfigs.map((item) => {
|
||||
if (!isArray(item.ipAddress)) item.ipAddress = [item.ipAddress]
|
||||
if (item.ipAddress.length) {
|
||||
if (this.prefixNodeIps[item.ipPoolId]) {
|
||||
this.prefixNodeIps[item.ipPoolId].push(...item.ipAddress)
|
||||
} else {
|
||||
this.prefixNodeIps[item.ipPoolId] = [...item.ipAddress]
|
||||
}
|
||||
}
|
||||
this.getIp(item, !!this.retention)
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
count() {
|
||||
this.configs.networkCardConfigs.map((item) => {
|
||||
this.getIp(item)
|
||||
})
|
||||
},
|
||||
'addData.networkRelations'() {
|
||||
this.getIpPool(true)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addCard() {
|
||||
this.configs.networkCardConfigs.push({
|
||||
operation: 'newNet',
|
||||
networkId: '',
|
||||
mac: '',
|
||||
model: 'VIRTIO',
|
||||
ipAddress: '', // 对应一个ip,可以为空
|
||||
netmask: '',
|
||||
link: 'up'
|
||||
})
|
||||
},
|
||||
|
||||
conditionIp(flag, cell = {}) {
|
||||
// 下拉框出现
|
||||
if (flag) return
|
||||
const { ipAddress = [], ipPoolId } = cell
|
||||
if (!ipAddress.length) return
|
||||
// 上一个审批节点时传输的,就已经是锁定状态了
|
||||
// 以 addresses 为基础, 过滤不在 prefixNodeIps 中的 ip
|
||||
const filterAddress = ipAddress.filter((ip) => !this.prefixNodeIps[cell.ipPoolId]?.includes(ip))
|
||||
if (!filterAddress.length) return
|
||||
conditionIp({
|
||||
condition: JSON.stringify({
|
||||
condition: 'checkIp',
|
||||
poolId: ipPoolId,
|
||||
ips: filterAddress
|
||||
})
|
||||
}).then((data) => {
|
||||
if (!data.success) {
|
||||
this.$message.success(data.message)
|
||||
cell.ipAddress = []
|
||||
}
|
||||
})
|
||||
},
|
||||
selectIpChange(cell) {
|
||||
if (!cell.ipAddress.length) cell.showIps = this.ippools.find(({ ipPoolId }) => ipPoolId === cell.ipPoolId).freezeAllIps.slice(0, 20)
|
||||
},
|
||||
calcAddress(cell, ip) {
|
||||
// 其他虚机中是否已经选择过 禁用
|
||||
let flag = false
|
||||
if (this.selectedIps.includes(ip)) flag = true
|
||||
// 超出数量禁用
|
||||
if (cell.ipAddress.length >= this.count) flag = true
|
||||
return flag
|
||||
},
|
||||
ipFilter(query = '', cell) {
|
||||
const freezeAllIps = this.ippools.find(({ ipPoolId }) => ipPoolId === cell.ipPoolId)?.freezeAllIps || []
|
||||
if (!query) return freezeAllIps.slice(0, 20)
|
||||
// 上个节点已选
|
||||
const preIps = this.prefixNodeIps[cell.ipPoolId]?.filter((ip) => ip.includes(query)) || []
|
||||
// 已过滤
|
||||
const arr = freezeAllIps.filter((ip) => ip.includes(query)).slice(0, 20)
|
||||
// 合并并去重
|
||||
this.$set(cell, 'showIps', uniq([...preIps, ...cell.ipAddress, ...arr]))
|
||||
return cell.showIps
|
||||
},
|
||||
|
||||
getIp(obj, isInit = false) {
|
||||
if (this.disabled) return
|
||||
const findIppool = this.ippools.find(({ ipPoolId }) => ipPoolId === obj.ipPoolId) || {}
|
||||
if (!obj.ipPoolId) {
|
||||
obj.ipAddress = []
|
||||
return
|
||||
}
|
||||
obj.loading = true
|
||||
conditionIp({ condition: JSON.stringify({ condition: 'listByPoolAndStatus', poolId: obj.ipPoolId, status: 'free' }) })
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
// 空闲的所有 ip
|
||||
// Vue 禁止响应式
|
||||
const freezeAllIps = Object.freeze(uniq([...(this.prefixNodeIps[obj.ipPoolId] || []), ...data.data.map(({ ip }) => ip)]))
|
||||
// 上一节点的虽然锁定,但是也是可选
|
||||
this.$set(findIppool, 'freezeAllIps', freezeAllIps)
|
||||
// 校验数量
|
||||
if (freezeAllIps.length) {
|
||||
const ipAddress = []
|
||||
freezeAllIps.forEach((ip) => {
|
||||
if (ipAddress.length === this.count) return
|
||||
if (isInit) {
|
||||
// 审批回显初始化
|
||||
ipAddress.push(...obj.ipAddress)
|
||||
} else if (obj.ipAddress.includes(ip) || !this.selectedIps.includes(ip)) {
|
||||
// 原本就在里面 || 没被使用过
|
||||
ipAddress.push(ip)
|
||||
}
|
||||
})
|
||||
obj.ipAddress = ipAddress
|
||||
this.$set(obj, 'showIps', freezeAllIps.slice(0, 20))
|
||||
} else {
|
||||
this.$message.warning('可用 IP 数量不足')
|
||||
obj.ipAddress = []
|
||||
obj.ipPoolId = ''
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => (obj.loading = false))
|
||||
},
|
||||
getIpPool(manual) {
|
||||
if (manual) {
|
||||
// 切换资源池回显
|
||||
this.addData.configs.networkCardConfigs.map((item) => {
|
||||
item.ipPoolId = ''
|
||||
})
|
||||
}
|
||||
this.ippools = []
|
||||
this.addData.networkRelations.forEach((item) => {
|
||||
this.ippools.push(cloneDeep(item))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
|
@ -0,0 +1,76 @@
|
|||
/** * Created by HaijunZhang on 2019/4/28. */
|
||||
<template>
|
||||
<div>
|
||||
<vc :type="type" v-if="type == 'VMWARE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></vc>
|
||||
<op :type="type" v-else-if="type == 'OPENSTACK'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></op>
|
||||
<easy-stack :type="type" v-else-if="type == 'EASYSTACK'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></easy-stack>
|
||||
<aws :type="type" v-else-if="type == 'AWS'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></aws>
|
||||
<ali :type="type" v-else-if="type == 'ALIYUN'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></ali>
|
||||
<huawei :type="type" v-else-if="type == 'HUAWEI'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></huawei>
|
||||
<tencent :type="type" v-else-if="type == 'TENCENT'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></tencent>
|
||||
<tce :type="type" v-else-if="type == 'TCE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></tce>
|
||||
<azure :type="type" v-if="type == 'AZURE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></azure>
|
||||
<smart :type="type" v-if="type == 'SMARTX'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></smart>
|
||||
<cloudTower :type="type" v-if="type == 'CLOUDTOWER'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></cloudTower>
|
||||
<fusion-sphere-clone :type="type" v-if="type == 'FUSIONSPHERE' && this.serverId" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></fusion-sphere-clone>
|
||||
<fusion-sphere :type="type" v-if="type == 'FUSIONSPHERE' && !this.serverId" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></fusion-sphere>
|
||||
<sangFor :type="type" v-if="type == 'SANGFOR'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></sangFor>
|
||||
<cnware :type="type" v-if="type == 'CNWARE'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></cnware>
|
||||
<zstack :type="type" v-if="type == 'ZSTACK'" @type="setType" :item-data="itemData" :disabled="disabled" ref="node"></zstack>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vc from './vc/index.vue'
|
||||
import op from './op/index.vue'
|
||||
import easyStack from './easyStack/index.vue'
|
||||
import ali from './ali/index.vue'
|
||||
import huawei from './huawei/index.vue'
|
||||
import tencent from './tencent/index.vue'
|
||||
import tce from './tce/index.vue'
|
||||
import aws from './aws/index.vue'
|
||||
import azure from './azure/index.vue'
|
||||
import smart from './smart/index.vue'
|
||||
import cloudTower from './cloudTower/index.vue'
|
||||
import fusionSphere from './fusionSphere/index.vue'
|
||||
import fusionSphereClone from './fusionSphere/clone.vue'
|
||||
import sangFor from './sangfor/index.vue'
|
||||
import cnware from './cnware/index.vue'
|
||||
import zstack from './zstack/index.vue'
|
||||
export default {
|
||||
props: {
|
||||
itemData: {
|
||||
type: [Object, Boolean]
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
components: { vc, ali, op, huawei, tencent, aws, tce, azure, smart, fusionSphere, fusionSphereClone, easyStack, cloudTower, sangFor, cnware, zstack },
|
||||
data() {
|
||||
return {
|
||||
type: '',
|
||||
serverId: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.itemData) {
|
||||
this.type = this.$route.params.type
|
||||
this.serverId = this.$route.params.serverId
|
||||
} else {
|
||||
this.type = this.itemData.location.vendorType
|
||||
this.serverId = this.itemData.configs.serverId
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setType(data) {
|
||||
this.type = data
|
||||
},
|
||||
getApplyData() {
|
||||
return this.$refs.node.getPostData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
|
@ -76,6 +76,12 @@ export default {
|
|||
} = this.addData
|
||||
// 代码容错处理
|
||||
if (!sku && !serviceItem) return 0
|
||||
if (['cloudtower.standard.volume', 'cnware.standard.volume'].includes(serviceCode) && item.name === '系统盘') {
|
||||
// cloudtower/cnware 系统盘根据基础定价来计算
|
||||
const basicPrice = JSON.parse(skuList[0]?.basicPrice || '[]')
|
||||
if (insAmount === 1) return 0
|
||||
return basicPrice[0].monthPrice * insAmount
|
||||
}
|
||||
const price = getPrice(sku || serviceItem, mode)
|
||||
// 集群部署多个实例
|
||||
const { nodes = 1 } = this.addData
|
||||
|
|
|
@ -9,6 +9,7 @@ export const statusList = [
|
|||
}
|
||||
]
|
||||
export const flowConfig = [
|
||||
{ category: 'EFCApplicationOperate', flowId: null },
|
||||
{ category: 'ApplicationOperate', flowId: null },
|
||||
{ category: 'AlterationOperate', flowId: null },
|
||||
{ category: 'ExtensionOperate', flowId: null },
|
||||
|
|
|
@ -7,8 +7,8 @@ function resolve(dir) {
|
|||
return path.join(__dirname, dir)
|
||||
}
|
||||
|
||||
const httpType = 'http://'
|
||||
const proxyUrl = '23.33.3.3:60006/' // 代理地址设置
|
||||
const httpType = 'https://'
|
||||
const proxyUrl = '23.33.3.22:60006/' // 代理地址设置
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
|
|
|
@ -9,8 +9,8 @@ const CompressPlugin = require('compress-webpack-plugin')
|
|||
function resolve(dir) {
|
||||
return path.join(__dirname, dir)
|
||||
}
|
||||
const httpType = 'http://'
|
||||
const proxyUrl = '23.33.3.3:60006/' // 代理地址设置
|
||||
const httpType = 'https://'
|
||||
const proxyUrl = '23.33.3.22:60006/' // 代理地址设置
|
||||
// const proxyUrl = '10.20.51.92:7001' // 代理地址设置
|
||||
// const proxyUrl = '10.10.2.60:50006/' 苏州代理地址
|
||||
module.exports = {
|
||||
|
|
Loading…
Reference in New Issue