fix: cloundTower代码迁移

develop
TangShan_DD 2024-05-28 15:53:11 +08:00
parent 3bbe3506e3
commit 1bf3dd6db8
39 changed files with 4477 additions and 43 deletions

View File

@ -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宿主机',

View File

@ -179,6 +179,7 @@ export function categoryFilter(value) {
ServiceSkuRevoke: '服务产品批量下架',
ServiceSkuBatchRelease: '服务产品上架',
ServiceSkuBatchRevoke: '服务产品批量下架',
EFCApplicationOperate: '服务使用申请',
ApplicationOperate: '服务使用申请',
AlterationOperate: '服务实例变更',
ExtensionOperate: '服务实例延期',

View File

@ -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)

View File

@ -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 }))
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}
}

View File

@ -0,0 +1,9 @@
::v-deep #cus-block {
.item {
color: #fff;
background-color: #5a94ff;
font-size: 20px;
font-weight: 700;
padding: 10px 0;
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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'

View File

@ -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

View File

@ -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 {

View File

@ -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'
}
}

View File

@ -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>

View File

@ -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: ''

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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 },

View File

@ -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: {

View File

@ -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 = {