feat: support disabling KVM hosts
This commit is contained in:
@@ -169,6 +169,13 @@ export const updateRemoteHost = (data) => {
|
||||
})
|
||||
}
|
||||
|
||||
/** 设置宿主机禁用状态 */
|
||||
export const setRemoteHostDisabled = (data) => {
|
||||
return http2.post('/api/v1/admin/server/host_service/point/host/set_disabled', data, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除宿主机 */
|
||||
export const deleteRemoteHost = (params) => {
|
||||
return http2.delete('/api/v1/admin/server/host_service/point/host/delete', { params })
|
||||
|
||||
@@ -859,7 +859,7 @@
|
||||
|
||||
<!-- 迁移虚拟机弹窗 -->
|
||||
<el-dialog v-model="migrateVisible" title="迁移虚拟机(更换宿主机)" width="560px" destroy-on-close>
|
||||
<el-alert type="warning" :closable="false" style="margin-bottom:16px">将虚拟机迁移到其他宿主机或宿主机组,请谨慎操作!</el-alert>
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom:16px">宿主机迁移为转移配置后重新开通,不保留原虚拟机数据,请谨慎操作!</el-alert>
|
||||
<el-form label-width="120px" v-loading="migrateOptionsLoading">
|
||||
<el-form-item label="迁移方式">
|
||||
<el-radio-group v-model="migrateMode">
|
||||
@@ -889,9 +889,9 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="到期顺延">
|
||||
<el-form-item label="到期顺延(天)">
|
||||
<el-input-number v-model="migrateForm.expire_offset_days" :min="0" :step="1" :precision="0" controls-position="right" style="width:100%" />
|
||||
<div style="font-size:12px;color:#909399">迁移完成后,用户商品到期时间向后顺延;0 表示不调整</div>
|
||||
<div style="font-size:12px;color:#909399">迁移完成后,用户商品到期时间向后顺延指定天数;0 表示不调整</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
</div>
|
||||
<div class="overview-actions">
|
||||
<!-- <el-button type="warning" plain @click="openTokenDialog"><el-icon><Key /></el-icon>创建注册令牌</el-button> -->
|
||||
<el-button :type="detail.disabled ? 'success' : 'warning'" plain :loading="disabledActionLoading" @click="handleSetDisabled">{{ detail.disabled ? '启用宿主机' : '禁用宿主机' }}</el-button>
|
||||
<el-button type="primary" plain @click="handleEdit">编辑宿主机</el-button>
|
||||
<el-button type="danger" plain @click="handleDelete">删除</el-button>
|
||||
</div>
|
||||
@@ -28,8 +29,8 @@
|
||||
<div class="status-item">
|
||||
<span class="status-label">状态</span>
|
||||
<span class="status-value">
|
||||
<span class="status-dot" :class="detail.is_active ? 'dot-running' : 'dot-other'"></span>
|
||||
{{ detail.is_active ? '启用' : '禁用' }}
|
||||
<span class="status-dot" :class="detail.disabled ? 'dot-disabled' : (detail.is_active ? 'dot-running' : 'dot-other')"></span>
|
||||
{{ detail.disabled ? '已禁用' : (detail.is_active ? '在线' : '离线') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
@@ -856,6 +857,12 @@
|
||||
<el-button v-if="formData.host_group_id" @click="formData.host_group_id = 0">清除</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="调度状态">
|
||||
<div>
|
||||
<el-switch v-model="formData.disabled" active-text="禁用" inactive-text="启用" />
|
||||
<div class="tk-form-tip">禁用后不参与自动调度,也不能作为新建或迁移目标;已有虚拟机不受影响。</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="介绍"><el-input v-model="formData.description" type="textarea" :rows="3" /></el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -1022,7 +1029,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, ArrowRight, Refresh, Edit, Delete, Monitor, Coin, Connection, Search, Plus, Key, CopyDocument, DataAnalysis } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getRemoteHostDetail, getRemoteHostMetrics, updateRemoteHost, deleteRemoteHost,
|
||||
getRemoteHostDetail, getRemoteHostMetrics, updateRemoteHost, deleteRemoteHost, setRemoteHostDisabled,
|
||||
getUserNetworkingList, getUserNetworkingDetail, createUserNetworking, deleteUserNetworking,
|
||||
assignUserNetworking, removeUserNetworkingNetwork,
|
||||
createHostToken, getMetricsHistory, getHostMetricsTop, getHostQuotaStats,
|
||||
@@ -1170,7 +1177,7 @@ const showHardwareInfo = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '',
|
||||
max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '',
|
||||
max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', disabled: false,
|
||||
...diskIoDefaults
|
||||
})
|
||||
const formRules = {
|
||||
@@ -1807,7 +1814,7 @@ const handleEdit = () => {
|
||||
port: d.port || 22, user: d.user || '', password: d.password || '', private_key: d.private_key || '',
|
||||
max_cpu: d.max_cpu || 0, max_memory: d.max_memory || 0, max_disk: d.max_disk || 0,
|
||||
rx_bandwidth: d.rx_bandwidth || 0, tx_bandwidth: d.tx_bandwidth || 0,
|
||||
host_group_id: d.host_group_id || 0, description: d.description || '',
|
||||
host_group_id: d.host_group_id || 0, description: d.description || '', disabled: !!d.disabled,
|
||||
...Object.fromEntries(diskIoFields.map(f => [f.key, d[f.key] ?? diskIoDefaults[f.key]]))
|
||||
})
|
||||
showDiskIoSection.value = diskIoFields.some(f => d[f.key] && d[f.key] !== diskIoDefaults[f.key])
|
||||
@@ -1820,18 +1827,42 @@ const handleSubmit = () => {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = { ...formData, service_id: serviceId.value, id: hostId.value }
|
||||
const disabled = payload.disabled
|
||||
delete payload.disabled
|
||||
if (!payload.token) delete payload.token
|
||||
if (!payload.password) delete payload.password
|
||||
if (!payload.private_key) delete payload.private_key
|
||||
if (!payload.description) delete payload.description
|
||||
if (!payload.host_group_id) delete payload.host_group_id
|
||||
const res = await updateRemoteHost(payload)
|
||||
if (res?.data?.code === 200) { ElMessage.success('修改成功'); editDialogVisible.value = false; loadDetail() }
|
||||
else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
if (res?.data?.code === 200) {
|
||||
const disabledRes = await setRemoteHostDisabled({ service_id: serviceId.value, id: hostId.value, disabled })
|
||||
if (disabledRes?.data?.code === 200) { ElMessage.success('修改成功'); editDialogVisible.value = false; loadDetail() }
|
||||
else ElMessage.error(extractApiError(disabledRes?.data, '设置禁用状态失败'))
|
||||
} else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { submitLoading.value = false }
|
||||
})
|
||||
}
|
||||
|
||||
const disabledActionLoading = ref(false)
|
||||
const handleSetDisabled = () => {
|
||||
if (!detail.value) return
|
||||
const disabled = !detail.value.disabled
|
||||
const action = disabled ? '禁用' : '启用'
|
||||
const message = disabled
|
||||
? `禁用宿主机「${detail.value.name}」后,它将不参与自动调度且不能作为新建或迁移目标,已有虚拟机不受影响。是否继续?`
|
||||
: `确定要启用宿主机「${detail.value.name}」参与自动调度吗?`
|
||||
ElMessageBox.confirm(message, `${action}宿主机`, { type: disabled ? 'warning' : 'info' }).then(async () => {
|
||||
disabledActionLoading.value = true
|
||||
try {
|
||||
const res = await setRemoteHostDisabled({ service_id: serviceId.value, id: hostId.value, disabled })
|
||||
if (res?.data?.code === 200) { ElMessage.success(`${action}成功`); loadDetail() }
|
||||
else ElMessage.error(extractApiError(res?.data, `${action}失败`))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${action}失败`)) }
|
||||
finally { disabledActionLoading.value = false }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
ElMessageBox.confirm(`确定要删除宿主机「${detail.value?.name}」吗?`, '删除确认', {
|
||||
confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning'
|
||||
@@ -2181,6 +2212,7 @@ onBeforeUnmount(() => { isPageActive = false; disposeCharts() })
|
||||
.status-value { font-size: 14px; color: #1d2129; font-weight: 500; display: flex; align-items: center; gap: 6px; }
|
||||
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||||
.dot-running { background: #00b42a; }
|
||||
.dot-disabled { background: #f53f3f; }
|
||||
.dot-other { background: #c9cdd4; }
|
||||
|
||||
.detail-tabs { background: #fff; border-radius: 4px; border: 1px solid #e8e8e8; padding: 0 24px; }
|
||||
|
||||
@@ -77,15 +77,17 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.is_active ? 'success' : 'danger'" size="small">{{ row.is_active ? '启用' : '禁用' }}</el-tag>
|
||||
<el-tag v-if="row.disabled" type="danger" size="small">已禁用</el-tag>
|
||||
<el-tag v-else :type="row.is_active ? 'success' : 'info'" size="small">{{ row.is_active ? '在线' : '离线' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" width="170">
|
||||
<template #default="{ row }">{{ formatTimestamp(row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<el-table-column label="操作" width="180" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="handleGoDetail(row)">编辑</el-button>
|
||||
<el-button link :type="row.disabled ? 'success' : 'warning'" :loading="disabledActionHostId === row.id" @click="handleSetDisabled(row)">{{ row.disabled ? '启用' : '禁用' }}</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -194,6 +196,12 @@
|
||||
<el-button v-if="formData.host_group_id" @click="formData.host_group_id = 0; formData._groupName = ''" style="margin-left: 4px">清除</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dialogType === 'edit'" label="调度状态">
|
||||
<div>
|
||||
<el-switch v-model="formData.disabled" active-text="禁用" inactive-text="启用" />
|
||||
<div class="tk-form-tip">禁用后不参与自动调度,也不能作为新建或迁移目标;已有虚拟机不受影响。</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="介绍">
|
||||
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="宿主机介绍(可选)" />
|
||||
</el-form-item>
|
||||
@@ -433,7 +441,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, ArrowLeft, ArrowRight, Monitor, Coin, Connection, Key, CopyDocument } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getRemoteHostList, getRemoteHostDetail,
|
||||
addRemoteHost, updateRemoteHost, deleteRemoteHost,
|
||||
addRemoteHost, updateRemoteHost, deleteRemoteHost, setRemoteHostDisabled,
|
||||
getHostGroupList, createHostToken, getMetricsHistory
|
||||
} from '@/api/admin/kvmService'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
@@ -450,6 +458,7 @@ const serviceName = computed(() => injectedServiceName?.value || route.query.ser
|
||||
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const disabledActionHostId = ref(null)
|
||||
const detailLoading = ref(false)
|
||||
const metricsLoading = ref(false)
|
||||
const hostList = ref([])
|
||||
@@ -510,7 +519,7 @@ const formData = reactive({
|
||||
id: undefined, name: '', base_url: '', ip: '', token: '',
|
||||
port: 22, user: '', password: '', private_key: '',
|
||||
max_cpu: 0, max_memory: 0, max_disk: 0,
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '',
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', disabled: false,
|
||||
_groupName: '',
|
||||
...diskIoDefaults
|
||||
})
|
||||
@@ -610,7 +619,7 @@ const resetForm = () => {
|
||||
id: undefined, name: '', base_url: '', ip: '', token: '',
|
||||
port: 22, user: '', password: '', private_key: '',
|
||||
max_cpu: 0, max_memory: 0, max_disk: 0,
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '',
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', disabled: false,
|
||||
_groupName: '', ...diskIoDefaults
|
||||
})
|
||||
showDiskIoSection.value = false
|
||||
@@ -675,7 +684,7 @@ const handleEdit = (row) => {
|
||||
port: row.port || 22, user: row.user || '', password: row.password || '', private_key: row.private_key || '',
|
||||
max_cpu: row.max_cpu || 0, max_memory: row.max_memory || 0, max_disk: row.max_disk || 0,
|
||||
rx_bandwidth: row.rx_bandwidth || 0, tx_bandwidth: row.tx_bandwidth || 0,
|
||||
host_group_id: row.host_group_id || 0, description: row.description || '',
|
||||
host_group_id: row.host_group_id || 0, description: row.description || '', disabled: !!row.disabled,
|
||||
_groupName: getGroupName(row.host_group_id),
|
||||
...Object.fromEntries(diskIoFields.map(f => [f.key, row[f.key] ?? diskIoDefaults[f.key]]))
|
||||
})
|
||||
@@ -687,6 +696,7 @@ const handleEdit = (row) => {
|
||||
if (detail.password) formData.password = detail.password
|
||||
if (detail.token) formData.token = detail.token
|
||||
if (detail.private_key) formData.private_key = detail.private_key
|
||||
formData.disabled = !!detail.disabled
|
||||
diskIoFields.forEach(f => { if (detail[f.key] !== undefined) formData[f.key] = detail[f.key] })
|
||||
}
|
||||
}).catch(() => {})
|
||||
@@ -699,6 +709,8 @@ const handleSubmit = () => {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = { ...formData, service_id: serviceId.value }
|
||||
const disabled = payload.disabled
|
||||
delete payload.disabled
|
||||
delete payload.id
|
||||
delete payload._groupName
|
||||
// 可选参数为空时不提交
|
||||
@@ -713,6 +725,9 @@ const handleSubmit = () => {
|
||||
} else {
|
||||
payload.id = formData.id
|
||||
res = await updateRemoteHost(payload)
|
||||
if (res?.data?.code === 200) {
|
||||
res = await setRemoteHostDisabled({ service_id: serviceId.value, id: formData.id, disabled })
|
||||
}
|
||||
}
|
||||
const body = res?.data
|
||||
if (body?.code === 200) {
|
||||
@@ -730,6 +745,23 @@ const handleSubmit = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleSetDisabled = (row) => {
|
||||
const disabled = !row.disabled
|
||||
const action = disabled ? '禁用' : '启用'
|
||||
const message = disabled
|
||||
? `禁用宿主机「${row.name}」后,它将不参与自动调度且不能作为新建或迁移目标,已有虚拟机不受影响。是否继续?`
|
||||
: `确定要启用宿主机「${row.name}」参与自动调度吗?`
|
||||
ElMessageBox.confirm(message, `${action}宿主机`, { type: disabled ? 'warning' : 'info' }).then(async () => {
|
||||
disabledActionHostId.value = row.id
|
||||
try {
|
||||
const res = await setRemoteHostDisabled({ service_id: serviceId.value, id: row.id, disabled })
|
||||
if (res?.data?.code === 200) { ElMessage.success(`${action}成功`); loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, `${action}失败`))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${action}失败`)) }
|
||||
finally { disabledActionHostId.value = null }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const handleViewDetail = async (row) => {
|
||||
detailVisible.value = true
|
||||
detailLoading.value = true
|
||||
|
||||
@@ -68,10 +68,11 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row._isHost" :type="row.is_active ? 'success' : 'info'" size="small">{{ row.is_active ? '在线' : '离线' }}</el-tag>
|
||||
<el-tag v-if="row._isHost && row.disabled" type="danger" size="small">已禁用</el-tag>
|
||||
<el-tag v-else-if="row._isHost" :type="row.is_active ? 'success' : 'info'" size="small">{{ row.is_active ? '在线' : '离线' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<el-table-column label="操作" width="330" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row._isGroup && !row._isUngrouped">
|
||||
<el-button link type="primary" size="small" @click="handleViewGroupDetail(row)">详情</el-button>
|
||||
@@ -83,6 +84,7 @@
|
||||
<template v-else-if="row._isHost">
|
||||
<el-button link type="primary" size="small" @click="handleGoHostDetail(row)">详情</el-button>
|
||||
<el-button link type="primary" size="small" @click="handleEditHost(row)">编辑</el-button>
|
||||
<el-button link :type="row.disabled ? 'success' : 'warning'" size="small" :loading="disabledActionHostId === row.id" @click="handleSetHostDisabled(row)">{{ row.disabled ? '启用' : '禁用' }}</el-button>
|
||||
<el-button link type="danger" size="small" @click="handleDeleteHost(row)">删除</el-button>
|
||||
</template>
|
||||
</template>
|
||||
@@ -388,6 +390,12 @@
|
||||
<el-option v-for="g in allGroups" :key="g.id" :value="g.id" :label="`${g.name} (ID: ${g.id})`" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="hostDialogType === 'edit'" label="调度状态">
|
||||
<div>
|
||||
<el-switch v-model="hostForm.disabled" active-text="禁用" inactive-text="启用" />
|
||||
<div class="tk-form-tip">禁用后不参与自动调度,也不能作为新建或迁移目标;已有虚拟机不受影响。</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="介绍">
|
||||
<el-input v-model="hostForm.description" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
@@ -414,6 +422,7 @@ import {
|
||||
getOptimalHostInfo,
|
||||
getRemoteHostList, getRemoteHostDetail,
|
||||
addRemoteHost, updateRemoteHost, deleteRemoteHost,
|
||||
setRemoteHostDisabled,
|
||||
createHostToken
|
||||
} from '@/api/admin/kvmService'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
@@ -429,6 +438,7 @@ const serviceName = computed(() => injectedServiceName?.value || route.query.ser
|
||||
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const disabledActionHostId = ref(null)
|
||||
const allGroups = ref([])
|
||||
const allHosts = ref([])
|
||||
const expandedGroupIds = ref(new Set())
|
||||
@@ -722,7 +732,7 @@ const hostForm = reactive({
|
||||
id: undefined, name: '', base_url: '', ip: '', token: '',
|
||||
port: 22, user: '', password: '', private_key: '',
|
||||
max_cpu: 0, max_memory: 0, max_disk: 0,
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '',
|
||||
rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', disabled: false,
|
||||
...diskIoDefaults
|
||||
})
|
||||
const hostFormRules = {
|
||||
@@ -733,7 +743,7 @@ const hostFormRules = {
|
||||
|
||||
const handleAddHost = () => {
|
||||
hostDialogType.value = 'add'
|
||||
Object.assign(hostForm, { id: undefined, name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '', max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', ...diskIoDefaults })
|
||||
Object.assign(hostForm, { id: undefined, name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '', max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '', disabled: false, ...diskIoDefaults })
|
||||
showHostDiskIo.value = false
|
||||
hostIoBwUnit.value = 'MB/s'
|
||||
hostDialogVisible.value = true
|
||||
@@ -741,7 +751,7 @@ const handleAddHost = () => {
|
||||
|
||||
const handleAddHostToGroup = (group) => {
|
||||
hostDialogType.value = 'add'
|
||||
Object.assign(hostForm, { id: undefined, name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '', max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: group.id, description: '', ...diskIoDefaults })
|
||||
Object.assign(hostForm, { id: undefined, name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '', max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: group.id, description: '', disabled: false, ...diskIoDefaults })
|
||||
showHostDiskIo.value = false
|
||||
hostIoBwUnit.value = 'MB/s'
|
||||
hostDialogVisible.value = true
|
||||
@@ -754,7 +764,7 @@ const handleEditHost = (row) => {
|
||||
port: row.port || 22, user: row.user || '', password: row.password || '', private_key: row.private_key || '',
|
||||
max_cpu: row.max_cpu || 0, max_memory: row.max_memory || 0, max_disk: row.max_disk || 0,
|
||||
rx_bandwidth: row.rx_bandwidth || 0, tx_bandwidth: row.tx_bandwidth || 0,
|
||||
host_group_id: row.host_group_id || 0, description: row.description || '',
|
||||
host_group_id: row.host_group_id || 0, description: row.description || '', disabled: !!row.disabled,
|
||||
...Object.fromEntries(diskIoFields.map(f => [f.key, row[f.key] ?? diskIoDefaults[f.key]]))
|
||||
})
|
||||
showHostDiskIo.value = diskIoFields.some(f => row[f.key] && row[f.key] !== diskIoDefaults[f.key])
|
||||
@@ -764,6 +774,7 @@ const handleEditHost = (row) => {
|
||||
if (d.password) hostForm.password = d.password
|
||||
if (d.token) hostForm.token = d.token
|
||||
if (d.private_key) hostForm.private_key = d.private_key
|
||||
hostForm.disabled = !!d.disabled
|
||||
diskIoFields.forEach(f => { if (d[f.key] !== undefined) hostForm[f.key] = d[f.key] })
|
||||
}
|
||||
}).catch(() => {})
|
||||
@@ -776,6 +787,8 @@ const submitHostForm = () => {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = { ...hostForm, service_id: serviceId.value }
|
||||
const disabled = payload.disabled
|
||||
delete payload.disabled
|
||||
delete payload.id
|
||||
if (!payload.token) delete payload.token
|
||||
if (!payload.password) delete payload.password
|
||||
@@ -788,6 +801,9 @@ const submitHostForm = () => {
|
||||
} else {
|
||||
payload.id = hostForm.id
|
||||
res = await updateRemoteHost(payload)
|
||||
if (res?.data?.code === 200) {
|
||||
res = await setRemoteHostDisabled({ service_id: serviceId.value, id: hostForm.id, disabled })
|
||||
}
|
||||
}
|
||||
if (res?.data?.code === 200) { ElMessage.success(hostDialogType.value === 'add' ? '新增成功' : '修改成功'); hostDialogVisible.value = false; loadTreeData() }
|
||||
else ElMessage.error(extractApiError(res?.data, '操作失败'))
|
||||
@@ -795,6 +811,23 @@ const submitHostForm = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleSetHostDisabled = (row) => {
|
||||
const disabled = !row.disabled
|
||||
const action = disabled ? '禁用' : '启用'
|
||||
const message = disabled
|
||||
? `禁用宿主机「${row.name}」后,它将不参与自动调度且不能作为新建或迁移目标,已有虚拟机不受影响。是否继续?`
|
||||
: `确定要启用宿主机「${row.name}」参与自动调度吗?`
|
||||
ElMessageBox.confirm(message, `${action}宿主机`, { type: disabled ? 'warning' : 'info' }).then(async () => {
|
||||
disabledActionHostId.value = row.id
|
||||
try {
|
||||
const res = await setRemoteHostDisabled({ service_id: serviceId.value, id: row.id, disabled })
|
||||
if (res?.data?.code === 200) { ElMessage.success(`${action}成功`); loadTreeData() }
|
||||
else ElMessage.error(extractApiError(res?.data, `${action}失败`))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${action}失败`)) }
|
||||
finally { disabledActionHostId.value = null }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const handleGoHostDetail = (row) => {
|
||||
router.push({ path: '/virtualization/host-detail', query: { service_id: serviceId.value, id: row.id, service_name: serviceName.value } })
|
||||
}
|
||||
|
||||
@@ -1189,7 +1189,7 @@
|
||||
<!-- 迁移虚拟机弹窗 -->
|
||||
<el-dialog v-model="migrateDialogVisible" title="迁移虚拟机(更换宿主机)" width="580px" destroy-on-close class="tk-dialog">
|
||||
<el-form label-width="100px" v-loading="migrateOptionsLoading">
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom: 16px">将虚拟机迁移到其他宿主机或宿主机组,请谨慎操作!</el-alert>
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom: 16px">宿主机迁移为转移配置后重新开通,不保留原虚拟机数据,请谨慎操作!</el-alert>
|
||||
<div class="tk-section">
|
||||
<div class="tk-section-title">迁移配置</div>
|
||||
<!-- <el-form-item label="当前宿主机">
|
||||
@@ -1219,7 +1219,7 @@
|
||||
<el-input-number v-model="migrateForm.ipv6_num" :min="0" controls-position="right" /><span class="tk-res-unit">个</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="到期顺延">
|
||||
<el-form-item label="到期顺延(天)">
|
||||
<el-input-number v-model="migrateForm.expire_offset_days" :min="0" :step="1" :precision="0" controls-position="right" />
|
||||
<span class="tk-res-unit">天(0 不顺延)</span>
|
||||
</el-form-item>
|
||||
@@ -1279,7 +1279,7 @@
|
||||
<el-input-number v-model="dataMigrateForm.ipv6_num" :min="0" controls-position="right" /><span class="tk-res-unit">个</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="到期顺延">
|
||||
<el-form-item label="到期顺延(天)">
|
||||
<el-input-number v-model="dataMigrateForm.expire_offset_days" :min="0" :step="1" :precision="0" controls-position="right" />
|
||||
<span class="tk-res-unit">天(0 不顺延)</span>
|
||||
</el-form-item>
|
||||
|
||||
@@ -473,9 +473,9 @@
|
||||
<UserListSelector v-model="showUserSelector" :current-user-id="createForm.user_id" @confirm="handleUserSelected" />
|
||||
|
||||
<!-- 批量宿主机迁移 -->
|
||||
<el-dialog v-model="batchHostMigrateVisible" title="批量宿主机迁移" width="620px" destroy-on-close class="tk-dialog">
|
||||
<el-dialog v-model="batchHostMigrateVisible" title="批量宿主机迁移(仅更换宿主机,不保留数据)" width="620px" destroy-on-close class="tk-dialog">
|
||||
<el-alert type="warning" :closable="false" show-icon style="margin-bottom:16px">
|
||||
将 {{ batchHostMigrateTargets.length }} 台虚拟机迁移到当前主控服务下的同一宿主机或宿主机组。任务将逐台提交,迁移中的实例会被跳过。
|
||||
宿主机迁移为转移配置后重新开通,不保留原虚拟机数据。将对 {{ batchHostMigrateTargets.length }} 台虚拟机逐台提交迁移,迁移中的实例会被跳过。
|
||||
</el-alert>
|
||||
<el-form :model="batchHostMigrateForm" label-width="110px">
|
||||
<div class="tk-section">
|
||||
@@ -501,7 +501,7 @@
|
||||
<el-form-item label="IPv4 数量"><el-input-number v-model="batchHostMigrateForm.ipv4_num" :min="0" controls-position="right" /><span class="tk-res-unit">个/实例</span></el-form-item>
|
||||
<el-form-item label="IPv6 数量"><el-input-number v-model="batchHostMigrateForm.ipv6_num" :min="0" controls-position="right" /><span class="tk-res-unit">个/实例</span></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="到期顺延">
|
||||
<el-form-item label="到期顺延(天)">
|
||||
<el-input-number v-model="batchHostMigrateForm.expire_offset_days" :min="0" :step="1" :precision="0" controls-position="right" />
|
||||
<span class="tk-res-unit">天/实例(0 不顺延)</span>
|
||||
</el-form-item>
|
||||
@@ -545,7 +545,7 @@
|
||||
<el-form-item label="IPv4 数量"><el-input-number v-model="batchMigrateForm.ipv4_num" :min="0" controls-position="right" /><span class="tk-res-unit">个/实例</span></el-form-item>
|
||||
<el-form-item label="IPv6 数量"><el-input-number v-model="batchMigrateForm.ipv6_num" :min="0" controls-position="right" /><span class="tk-res-unit">个/实例</span></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="到期顺延">
|
||||
<el-form-item label="到期顺延(天)">
|
||||
<el-input-number v-model="batchMigrateForm.expire_offset_days" :min="0" :step="1" :precision="0" controls-position="right" />
|
||||
<span class="tk-res-unit">天/实例(0 不顺延)</span>
|
||||
</el-form-item>
|
||||
|
||||
Reference in New Issue
Block a user