feat: 添加用户虚拟机商品管理
This commit is contained in:
@@ -321,6 +321,26 @@
|
||||
<HostGroupSelectorPopup v-model="showHostGroupSelector" :service-id="serviceId" :current-id="createForm.host_group_id" @confirm="handleHostGroupSelected" />
|
||||
<!-- 用户选择器 -->
|
||||
<UserListSelector v-model="showUserSelector" :current-user-id="createForm.user_id" @confirm="handleUserSelected" />
|
||||
|
||||
<!-- 电源操作确认弹窗 -->
|
||||
<el-dialog v-model="powerDialogVisible" :title="`${powerLabels[powerAction] || ''}虚拟机`" width="400px" destroy-on-close>
|
||||
<div style="display: flex; align-items: flex-start; gap: 12px; padding: 8px 0">
|
||||
<el-icon :size="22" :style="{ color: powerAction === 'stop' ? '#F56C6C' : powerAction === 'reboot' ? '#E6A23C' : '#409EFF', flexShrink: 0, marginTop: '2px' }">
|
||||
<WarningFilled />
|
||||
</el-icon>
|
||||
<div>
|
||||
<div style="font-size: 15px; font-weight: 500; color: #303133; margin-bottom: 12px">
|
||||
确定要{{ powerLabels[powerAction] }}虚拟机「{{ powerRow?.name }}」吗?
|
||||
</div>
|
||||
<el-checkbox v-model="powerForce" style="margin-bottom: 4px">强制执行</el-checkbox>
|
||||
<div style="font-size: 12px; color: #909399; padding-left: 24px">勾选后将强制{{ powerLabels[powerAction] }},可能导致数据丢失</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="powerDialogVisible = false">取消</el-button>
|
||||
<el-button :type="powerAction === 'stop' ? 'danger' : 'primary'" @click="submitPower">确定{{ powerLabels[powerAction] }}</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -328,7 +348,7 @@
|
||||
import { ref, reactive, computed, inject, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Refresh, Search, ArrowLeft, ArrowDown } from '@element-plus/icons-vue'
|
||||
import { Plus, Refresh, Search, ArrowLeft, ArrowDown, WarningFilled } from '@element-plus/icons-vue'
|
||||
import {
|
||||
getRemoteHostList, getVmList, getVmDetail, getVmStatus, getVmMetrics,
|
||||
createVm, rebuildVm, startVm, stopVm, rebootVm, suspendVm,
|
||||
@@ -438,6 +458,7 @@ const vmStatuses = [
|
||||
{ label: '等待中', value: 'pending' }, { label: '创建中', value: 'creating' },
|
||||
{ label: '就绪', value: 'ready' }, { label: '运行中', value: 'running' },
|
||||
{ label: '已停止', value: 'stopped' }, { label: '已停止', value: 'stop' },
|
||||
{ label: '已关闭', value: 'shutoff' },
|
||||
{ label: '错误', value: 'error' }, { label: '已暂停', value: 'paused' },
|
||||
{ label: '重启中', value: 'reboot' }, { label: '已关机', value: 'poweroff' },
|
||||
{ label: '未知', value: 'unknown' }
|
||||
@@ -470,13 +491,13 @@ const createRules = {
|
||||
|
||||
const vmStatusType = (s) => ({
|
||||
running: 'success', ready: 'success', creating: 'warning', pending: 'info',
|
||||
stopped: 'danger', stop: 'danger', error: 'danger', paused: 'warning',
|
||||
stopped: 'danger', stop: 'danger', shutoff: 'danger', error: 'danger', paused: 'warning',
|
||||
reboot: 'warning', poweroff: 'info', unknown: 'info'
|
||||
}[s] || 'info')
|
||||
|
||||
const vmStatusLabel = (s) => ({
|
||||
running: '运行中', ready: '就绪', creating: '创建中', pending: '等待中',
|
||||
stopped: '已停止', stop: '已停止', error: '错误', paused: '已暂停',
|
||||
stopped: '已停止', stop: '已停止', shutoff: '已关闭', error: '错误', paused: '已暂停',
|
||||
reboot: '重启中', poweroff: '已关机', unknown: '未知'
|
||||
}[s] || s || '-')
|
||||
|
||||
@@ -589,29 +610,34 @@ const submitCreate = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const powerDialogVisible = ref(false)
|
||||
const powerAction = ref('')
|
||||
const powerRow = ref(null)
|
||||
const powerForce = ref(false)
|
||||
const powerLabels = { start: '启动', stop: '停止', reboot: '重启', suspend: '暂停', resume: '恢复' }
|
||||
|
||||
const handlePower = (row, action) => {
|
||||
const labels = { start: '启动', stop: '停止', reboot: '重启', suspend: '暂停', resume: '恢复' }
|
||||
ElMessageBox.confirm(`确定要${labels[action]}虚拟机「${row.name}」吗?`, `${labels[action]}确认`, {
|
||||
confirmButtonText: '确定', cancelButtonText: '取消',
|
||||
type: action === 'stop' ? 'warning' : 'info'
|
||||
}).then(async () => {
|
||||
try {
|
||||
const apis = { start: startVm, stop: stopVm, reboot: rebootVm, suspend: suspendVm, resume: resumeVm }
|
||||
const payload = { service_id: serviceId.value, vm_id: row.id }
|
||||
// resume uses FormData
|
||||
let res
|
||||
if (action === 'resume') {
|
||||
const fd = new FormData()
|
||||
fd.append('service_id', serviceId.value)
|
||||
fd.append('vm_id', row.id)
|
||||
res = await resumeVm(fd)
|
||||
} else {
|
||||
res = await apis[action](payload)
|
||||
}
|
||||
if (res?.data?.code === 200) { ElMessage.success(`${labels[action]}成功`); loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, `${labels[action]}失败`))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${labels[action]}失败`)) }
|
||||
}).catch(() => {})
|
||||
powerRow.value = row
|
||||
powerAction.value = action
|
||||
powerForce.value = false
|
||||
powerDialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitPower = async () => {
|
||||
const action = powerAction.value
|
||||
const row = powerRow.value
|
||||
const label = powerLabels[action]
|
||||
powerDialogVisible.value = false
|
||||
try {
|
||||
const apis = { start: startVm, stop: stopVm, reboot: rebootVm, suspend: suspendVm, resume: resumeVm }
|
||||
const fd = new FormData()
|
||||
fd.append('service_id', serviceId.value)
|
||||
fd.append('vm_id', row.id)
|
||||
if (powerForce.value) fd.append('force', true)
|
||||
const res = await apis[action](fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success(`${label}成功`); loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, `${label}失败`))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, `${label}失败`)) }
|
||||
}
|
||||
|
||||
const handleMoreAction = (row, command) => {
|
||||
@@ -635,7 +661,11 @@ const submitRebuild = async () => {
|
||||
if (!rebuildImageId.value) { ElMessage.warning('请选择镜像'); return }
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const res = await rebuildVm({ service_id: serviceId.value, vm_id: rebuildTarget.value.id, image_id: rebuildImageId.value })
|
||||
const fd = new FormData()
|
||||
fd.append('service_id', serviceId.value)
|
||||
fd.append('vm_id', rebuildTarget.value.id)
|
||||
fd.append('image_id', rebuildImageId.value)
|
||||
const res = await rebuildVm(fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success('重装成功'); rebuildDialogVisible.value = false; loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, '重装失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '重装失败')) } finally { submitLoading.value = false }
|
||||
@@ -695,9 +725,12 @@ const fetchVmStatus = async (vm) => {
|
||||
try {
|
||||
const res = await getVmStatus({ service_id: serviceId.value, vm_id: vm.id })
|
||||
if (res?.data?.code === 200 && res?.data?.data) {
|
||||
const statusData = res.data.data
|
||||
currentDetail.value = { ...currentDetail.value, status: statusData.status ?? statusData }
|
||||
ElMessage.success('状态已刷新: ' + vmStatusLabel(currentDetail.value.status))
|
||||
const outer = res.data.data
|
||||
const inner = outer.data ?? outer
|
||||
const state = inner.state ?? inner.status ?? inner
|
||||
const desc = inner.desc || ''
|
||||
currentDetail.value = { ...currentDetail.value, status: state }
|
||||
ElMessage.success(`状态已刷新: ${desc || vmStatusLabel(state)}`)
|
||||
}
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取状态失败')) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user