feat: 虚拟机流量精细化控制接入(接口新增,待联调)
1. userVm.js/kvmService.js 新增 traffic_policy 系列 API(GET/update/add_fixed/add_temporary) 2. UserVmList.vue/VmManage.vue 创建表单新增 traffic_max、traffic_exhausted_rx/tx_mbps 三个可选字段 3. UserVmDetail.vue/VmDetail.vue 修改带宽表单新增耗尽限速字段,并各增加流量策略 Tab(展示+修改策略+增加固定/临时流量) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -401,6 +401,17 @@ export const updateVmTraffic = (data) => {
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 流量策略 ==========
|
||||
// 测试未通过(接口新增,待联调)
|
||||
/** 获取虚拟机流量策略 */
|
||||
export const getVmTrafficPolicy = (params) => http2.get('/api/v1/admin/server/host_service/point/vm/traffic_policy', { params })
|
||||
/** 修改虚拟机流量策略 */
|
||||
export const updateVmTrafficPolicy = (data) => http2.post('/api/v1/admin/server/host_service/point/vm/traffic_policy/update', data, { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
/** 增加虚拟机固定流量上限 */
|
||||
export const addVmFixedTraffic = (data) => http2.post('/api/v1/admin/server/host_service/point/vm/traffic_policy/add_fixed', data, { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
/** 增加虚拟机一次性临时流量 */
|
||||
export const addVmTemporaryTraffic = (data) => http2.post('/api/v1/admin/server/host_service/point/vm/traffic_policy/add_temporary', data, { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
|
||||
/** 启动虚拟机 */
|
||||
export const startVm = (data) => {
|
||||
return http2.post('/api/v1/admin/server/host_service/point/vm/start', data, {
|
||||
|
||||
@@ -106,6 +106,13 @@ export const deleteUserGoods = (params) => http2.delete(`${GOODS_BASE}/delete`,
|
||||
|
||||
export const getUserVmMetricsHistory = (params) => http2.get(`${BASE}/metrics_history`, { params })
|
||||
|
||||
// ========== 流量策略 ==========
|
||||
// 测试未通过(接口新增,待联调)
|
||||
export const getUserVmTrafficPolicy = (params) => http2.get(`${BASE}/traffic_policy`, { params })
|
||||
export const updateUserVmTrafficPolicy = (data) => http2.post(`${BASE}/traffic_policy/update`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
export const addUserVmFixedTraffic = (data) => http2.post(`${BASE}/traffic_policy/add_fixed`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
export const addUserVmTemporaryTraffic = (data) => http2.post(`${BASE}/traffic_policy/add_temporary`, fd(data), { headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
|
||||
// ========== 到期提醒 ==========
|
||||
export const getExpireRemindList = (params) => http2.get(`${GOODS_BASE}/expire_remind/list`, { params })
|
||||
export const sendExpireRemind = (data) => http2.post(`${GOODS_BASE}/expire_remind/send`, data, { headers: { 'Content-Type': 'application/json' } })
|
||||
|
||||
@@ -434,6 +434,27 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流量策略 -->
|
||||
<el-tab-pane v-if="isVmGoods" label="流量策略" name="trafficPolicy">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">流量策略</h3>
|
||||
<div style="display:flex;gap:8px">
|
||||
<el-button size="small" type="primary" @click="openTrafficPolicyDialog">修改流量策略</el-button>
|
||||
<el-button size="small" type="success" @click="openAddTrafficDialog('fixed')">增加固定流量</el-button>
|
||||
<el-button size="small" type="warning" @click="openAddTrafficDialog('temporary')">增加临时流量</el-button>
|
||||
<el-button size="small" :icon="Refresh" @click="loadTrafficPolicy" :loading="trafficPolicyLoading">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-descriptions v-if="trafficPolicy" :column="3" border size="small" style="margin-top:12px">
|
||||
<el-descriptions-item label="流量上限">{{ trafficPolicy.traffic_max_mb != null ? (trafficPolicy.traffic_max_mb === 0 ? '不限' : trafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽下行限速">{{ trafficPolicy.exhausted_rx_mbps != null ? (trafficPolicy.exhausted_rx_mbps === 0 ? '不限' : trafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽上行限速">{{ trafficPolicy.exhausted_tx_mbps != null ? (trafficPolicy.exhausted_tx_mbps === 0 ? '不限' : trafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else-if="!trafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
@@ -647,6 +668,18 @@
|
||||
</el-select>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="trafficForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="trafficForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="trafficVisible = false">取消</el-button>
|
||||
@@ -654,6 +687,50 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 流量策略管理 -->
|
||||
<el-dialog v-model="trafficPolicyVisible" title="修改流量策略" width="440px" destroy-on-close>
|
||||
<el-form :model="trafficPolicyForm" label-width="120px">
|
||||
<el-form-item label="流量上限">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="trafficPolicyForm.traffic_max_mb" :min="0" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">MB(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="trafficPolicyForm.exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="trafficPolicyForm.exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="trafficPolicyVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="trafficPolicyLoading" @click="submitUpdateTrafficPolicy">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 增加固定/临时流量 -->
|
||||
<el-dialog v-model="addTrafficVisible" :title="addTrafficType === 'fixed' ? '增加固定流量上限' : '增加一次性临时流量'" width="400px" destroy-on-close>
|
||||
<el-form :model="addTrafficForm" label-width="110px">
|
||||
<el-form-item label="流量数量">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="addTrafficForm.traffic_mb" :min="1" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text" style="margin-left:8px">MB</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="addTrafficVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="trafficPolicyLoading" @click="submitAddTraffic">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 转移用户 -->
|
||||
<el-dialog v-model="transferVisible" title="转移虚拟机" width="440px" destroy-on-close>
|
||||
<el-form label-width="100px">
|
||||
@@ -1008,7 +1085,8 @@ import {
|
||||
getUserVmPostGroupDetail,
|
||||
getUserVmNetworkList, getUserVmNetworkingList, createUserVmNetworking, assignUserVmNetworking, removeUserVmNetworkingNetwork, deleteUserVmNetworking,
|
||||
getUserGoodsDetail,
|
||||
getUserVmMetricsHistory
|
||||
getUserVmMetricsHistory,
|
||||
getUserVmTrafficPolicy, updateUserVmTrafficPolicy, addUserVmFixedTraffic, addUserVmTemporaryTraffic
|
||||
} from '@/api/admin/userVm'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
import { vmStatusLabel as vmStatusLabelUtil, vmStatusType as vmStatusTypeUtil, volumeStatusLabel, volumeStatusType } from '@/utils/tool'
|
||||
@@ -1184,6 +1262,7 @@ const handleTabChange = (tab) => {
|
||||
if (tab === 'security') loadSgLockInfo()
|
||||
if (tab === 'networking') loadNetworkings()
|
||||
if (tab === 'monitor' && !metricsData.value) loadMetricsHistory()
|
||||
if (tab === 'trafficPolicy') loadTrafficPolicy()
|
||||
}
|
||||
|
||||
// 请求安全组详情补充 lock 字段(使用用户虚拟机安全组详情接口)
|
||||
@@ -1234,7 +1313,7 @@ const submitPower = async () => {
|
||||
const handleMoreCmd = (cmd) => {
|
||||
if (powerLabels[cmd]) { handlePower(cmd); return }
|
||||
if (cmd === 'rebuild') { rebuildImageId.value = 0; rebuildImageName.value = ''; rebuildImages.value = []; rebuildVisible.value = true; loadRebuildImages() }
|
||||
if (cmd === 'updateTraffic') { Object.assign(trafficForm, { rx_bandwidth: vm.value?.rx_bandwidth || 0, tx_bandwidth: vm.value?.tx_bandwidth || 0, _trafficValue: +((vm.value?.traffic_max || 0) / 1024).toFixed(2), _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB' }); trafficVisible.value = true }
|
||||
if (cmd === 'updateTraffic') { Object.assign(trafficForm, { rx_bandwidth: vm.value?.rx_bandwidth || 0, tx_bandwidth: vm.value?.tx_bandwidth || 0, _trafficValue: +((vm.value?.traffic_max || 0) / 1024).toFixed(2), _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 }); trafficVisible.value = true }
|
||||
if (cmd === 'transfer') { Object.assign(transferForm, { target_user_id: 0, _userName: '' }); transferVisible.value = true }
|
||||
if (cmd === 'updateVm') openEditVm()
|
||||
if (cmd === 'refactorVm') openRefactorVm()
|
||||
@@ -1897,7 +1976,7 @@ const submitEditVm = async () => {
|
||||
|
||||
// ---- 修改带宽 ----
|
||||
const trafficVisible = ref(false)
|
||||
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficValue: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB' })
|
||||
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficValue: 0, _rxUnit: 'Mbps', _txUnit: 'Mbps', _trafficUnit: 'GB', traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 })
|
||||
const submitUpdateTraffic = async () => {
|
||||
actionLoading.value = true
|
||||
try {
|
||||
@@ -1912,12 +1991,73 @@ const submitUpdateTraffic = async () => {
|
||||
tx_bandwidth: Math.round(txBw),
|
||||
traffic_max: Math.round(trafficMb)
|
||||
}
|
||||
if (trafficForm.traffic_exhausted_rx_mbps > 0) payload.traffic_exhausted_rx_mbps = trafficForm.traffic_exhausted_rx_mbps
|
||||
if (trafficForm.traffic_exhausted_tx_mbps > 0) payload.traffic_exhausted_tx_mbps = trafficForm.traffic_exhausted_tx_mbps
|
||||
const res = await updateUserVmTraffic(payload)
|
||||
if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficVisible.value = false; loadDetail() }
|
||||
else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
|
||||
}
|
||||
|
||||
// ---- 流量策略 ----
|
||||
// 测试未通过(接口新增,待联调)
|
||||
const trafficPolicy = ref(null)
|
||||
const trafficPolicyLoading = ref(false)
|
||||
const trafficPolicyVisible = ref(false)
|
||||
const addTrafficVisible = ref(false)
|
||||
const addTrafficType = ref('fixed')
|
||||
const trafficPolicyForm = reactive({ traffic_max_mb: 0, exhausted_rx_mbps: 0, exhausted_tx_mbps: 0 })
|
||||
const addTrafficForm = reactive({ traffic_mb: 1 })
|
||||
|
||||
const loadTrafficPolicy = async () => {
|
||||
if (!userGoodsId.value) return
|
||||
trafficPolicyLoading.value = true
|
||||
try {
|
||||
const res = await getUserVmTrafficPolicy({ user_goods_id: userGoodsId.value })
|
||||
if (res?.data?.code === 200) trafficPolicy.value = res.data.data
|
||||
} catch { /* ignore */ } finally { trafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
const openTrafficPolicyDialog = () => {
|
||||
Object.assign(trafficPolicyForm, {
|
||||
traffic_max_mb: trafficPolicy.value?.traffic_max_mb || 0,
|
||||
exhausted_rx_mbps: trafficPolicy.value?.exhausted_rx_mbps || 0,
|
||||
exhausted_tx_mbps: trafficPolicy.value?.exhausted_tx_mbps || 0
|
||||
})
|
||||
trafficPolicyVisible.value = true
|
||||
}
|
||||
|
||||
const submitUpdateTrafficPolicy = async () => {
|
||||
trafficPolicyLoading.value = true
|
||||
try {
|
||||
const res = await updateUserVmTrafficPolicy({
|
||||
user_goods_id: userGoodsId.value,
|
||||
traffic_max_mb: trafficPolicyForm.traffic_max_mb,
|
||||
exhausted_rx_mbps: trafficPolicyForm.exhausted_rx_mbps,
|
||||
exhausted_tx_mbps: trafficPolicyForm.exhausted_tx_mbps
|
||||
})
|
||||
if (res?.data?.code === 200) { ElMessage.success('修改成功'); trafficPolicyVisible.value = false; loadTrafficPolicy() }
|
||||
else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { trafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
const openAddTrafficDialog = (type) => {
|
||||
addTrafficType.value = type
|
||||
addTrafficForm.traffic_mb = 1
|
||||
addTrafficVisible.value = true
|
||||
}
|
||||
|
||||
const submitAddTraffic = async () => {
|
||||
if (!addTrafficForm.traffic_mb || addTrafficForm.traffic_mb < 1) { ElMessage.warning('请输入有效的流量数量'); return }
|
||||
trafficPolicyLoading.value = true
|
||||
try {
|
||||
const apiFn = addTrafficType.value === 'fixed' ? addUserVmFixedTraffic : addUserVmTemporaryTraffic
|
||||
const res = await apiFn({ user_goods_id: userGoodsId.value, traffic_mb: addTrafficForm.traffic_mb })
|
||||
if (res?.data?.code === 200) { ElMessage.success('操作成功'); addTrafficVisible.value = false; loadTrafficPolicy() }
|
||||
else ElMessage.error(extractApiError(res?.data, '操作失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { trafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
// ---- 转移 ----
|
||||
const transferVisible = ref(false)
|
||||
const showTransferUserSelector = ref(false)
|
||||
|
||||
@@ -273,6 +273,40 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 流量策略(可选) -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="流量上限">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="createForm.traffic_max" :min="0" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text">MB</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:2px">0 表示不限流量</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="createForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text">Mbps</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:2px">流量耗尽后下行速率,0 表示不限</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<div class="unit-input-row">
|
||||
<el-input-number v-model="createForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" style="flex:1" />
|
||||
<span class="unit-text">Mbps</span>
|
||||
</div>
|
||||
<div style="font-size:12px;color:#909399;margin-top:2px">流量耗尽后上行速率,0 表示不限</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 行9: 续费价格 + 基础价格 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
@@ -1090,7 +1124,8 @@ const createForm = reactive({
|
||||
network_ids: [], _networkNames: '',
|
||||
ipv4_num: 0, ipv6_num: 0, snapshot_num: 0, backup_num: 0,
|
||||
_renewPriceYuan: 0, _basePriceYuan: 0,
|
||||
data_volume_size: 0, note: '', expire_time: ''
|
||||
data_volume_size: 0, note: '', expire_time: '',
|
||||
traffic_max: 0, traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0
|
||||
})
|
||||
|
||||
const createRules = {
|
||||
@@ -1116,7 +1151,8 @@ const handleCreate = (tab = 'create') => {
|
||||
host_id: 0, _hostName: '', host_group_id: 0, _hostGroupName: '',
|
||||
network_ids: [], _networkNames: '',
|
||||
ipv4_num: 0, ipv6_num: 0, snapshot_num: 0, backup_num: 0,
|
||||
_renewPriceYuan: 0, _basePriceYuan: 0, data_volume_size: 0, note: '', expire_time: ''
|
||||
_renewPriceYuan: 0, _basePriceYuan: 0, data_volume_size: 0, note: '', expire_time: '',
|
||||
traffic_max: 0, traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0
|
||||
})
|
||||
resetBindGoodsForm()
|
||||
pendingTab = tab
|
||||
@@ -1213,6 +1249,9 @@ const submitCreate = () => {
|
||||
if (createForm.host_id) payload.host_id = createForm.host_id
|
||||
if (createForm.host_group_id) payload.host_group_id = createForm.host_group_id
|
||||
if (createForm.network_ids.length) payload.network_ids = createForm.network_ids
|
||||
if (createForm.traffic_max > 0) payload.traffic_max = createForm.traffic_max
|
||||
if (createForm.traffic_exhausted_rx_mbps > 0) payload.traffic_exhausted_rx_mbps = createForm.traffic_exhausted_rx_mbps
|
||||
if (createForm.traffic_exhausted_tx_mbps > 0) payload.traffic_exhausted_tx_mbps = createForm.traffic_exhausted_tx_mbps
|
||||
const res = await createUserVm(payload)
|
||||
if (res?.data?.code === 200) { ElMessage.success('创建成功'); createVisible.value = false; loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, '创建失败'))
|
||||
|
||||
@@ -641,6 +641,28 @@
|
||||
<el-empty v-else-if="!historicalMetricsLoading" description="加载监控数据中..." :image-size="80" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 流量策略 -->
|
||||
<el-tab-pane label="流量策略" name="vmTrafficPolicy">
|
||||
<div class="section-block">
|
||||
<div class="section-header">
|
||||
<h3 class="section-title">流量策略</h3>
|
||||
<div style="display:flex;gap:8px">
|
||||
<el-button size="small" type="primary" @click="openVmTrafficPolicyDialog">修改流量策略</el-button>
|
||||
<el-button size="small" type="success" @click="openVmAddTrafficDialog('fixed')">增加固定流量</el-button>
|
||||
<el-button size="small" type="warning" @click="openVmAddTrafficDialog('temporary')">增加临时流量</el-button>
|
||||
<el-button size="small" :icon="Refresh" @click="loadVmTrafficPolicy" :loading="vmTrafficPolicyLoading">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-descriptions v-if="vmTrafficPolicy" :column="3" border size="small" style="margin-top:12px">
|
||||
<el-descriptions-item label="流量上限">{{ vmTrafficPolicy.traffic_max_mb != null ? (vmTrafficPolicy.traffic_max_mb === 0 ? '不限' : vmTrafficPolicy.traffic_max_mb + ' MB') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽下行限速">{{ vmTrafficPolicy.exhausted_rx_mbps != null ? (vmTrafficPolicy.exhausted_rx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_rx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗尽上行限速">{{ vmTrafficPolicy.exhausted_tx_mbps != null ? (vmTrafficPolicy.exhausted_tx_mbps === 0 ? '不限' : vmTrafficPolicy.exhausted_tx_mbps + ' Mbps') : '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-empty v-else-if="!vmTrafficPolicyLoading" description="暂无流量策略数据" :image-size="60" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
@@ -958,6 +980,16 @@
|
||||
<el-input-number v-model="trafficForm._trafficGB" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">GB</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="trafficForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="trafficForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -968,6 +1000,50 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 流量策略管理弹窗 -->
|
||||
<el-dialog v-model="vmTrafficPolicyVisible" title="修改流量策略" width="480px" destroy-on-close class="tk-dialog">
|
||||
<el-form :model="vmTrafficPolicyForm" label-width="120px">
|
||||
<el-form-item label="流量上限">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="vmTrafficPolicyForm.traffic_max_mb" :min="0" controls-position="right" /><span class="tk-res-unit">MB(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="vmTrafficPolicyForm.exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="vmTrafficPolicyForm.exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="tk-dialog-footer">
|
||||
<el-button @click="vmTrafficPolicyVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="vmTrafficPolicyLoading" @click="submitUpdateVmTrafficPolicy">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 增加固定/临时流量弹窗 -->
|
||||
<el-dialog v-model="vmAddTrafficVisible" :title="vmAddTrafficType === 'fixed' ? '增加固定流量上限' : '增加一次性临时流量'" width="400px" destroy-on-close class="tk-dialog">
|
||||
<el-form :model="vmAddTrafficForm" label-width="110px">
|
||||
<el-form-item label="流量数量">
|
||||
<div class="tk-inline-unit">
|
||||
<el-input-number v-model="vmAddTrafficForm.traffic_mb" :min="1" controls-position="right" /><span class="tk-res-unit">MB</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="tk-dialog-footer">
|
||||
<el-button @click="vmAddTrafficVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="vmTrafficPolicyLoading" @click="submitAddVmTraffic">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 迁移虚拟机弹窗 -->
|
||||
<el-dialog v-model="migrateDialogVisible" title="迁移虚拟机(更换宿主机)" width="580px" destroy-on-close class="tk-dialog">
|
||||
<el-form label-width="100px" v-loading="migrateOptionsLoading">
|
||||
@@ -1506,7 +1582,8 @@ import {
|
||||
getBackupList, createBackup, restoreBackup, deleteBackup, getBackupProgress, getBackupCount, setBackupLimit,
|
||||
migrateVm, getRemoteHostGroupList, getRemoteHostDetail,
|
||||
dataMigrateVm, getDataMigrateProgress, abortDataMigrate,
|
||||
getKvmServiceList, getMetricsHistory, getNetworkList
|
||||
getKvmServiceList, getMetricsHistory, getNetworkList,
|
||||
getVmTrafficPolicy, updateVmTrafficPolicy, addVmFixedTraffic, addVmTemporaryTraffic
|
||||
} from '@/api/admin/kvmService'
|
||||
import { getUserInfo } from '@/api/admin/user'
|
||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||
@@ -2159,14 +2236,16 @@ const submitRefactorVm = async () => {
|
||||
|
||||
// ---- 修改带宽 ----
|
||||
const trafficDialogVisible = ref(false)
|
||||
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficGB: 0 })
|
||||
const trafficForm = reactive({ rx_bandwidth: 0, tx_bandwidth: 0, _trafficGB: 0, traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0 })
|
||||
|
||||
const handleUpdateTraffic = () => {
|
||||
if (!detail.value) return
|
||||
Object.assign(trafficForm, {
|
||||
rx_bandwidth: detail.value.rx_bandwidth || 0,
|
||||
tx_bandwidth: detail.value.tx_bandwidth || 0,
|
||||
_trafficGB: ((detail.value.traffic_max || 0) / 1024).toFixed(2) * 1
|
||||
_trafficGB: ((detail.value.traffic_max || 0) / 1024).toFixed(2) * 1,
|
||||
traffic_exhausted_rx_mbps: 0,
|
||||
traffic_exhausted_tx_mbps: 0
|
||||
})
|
||||
trafficDialogVisible.value = true
|
||||
}
|
||||
@@ -2180,12 +2259,78 @@ const submitUpdateTraffic = async () => {
|
||||
fd.append('rx_bandwidth', trafficForm.rx_bandwidth)
|
||||
fd.append('tx_bandwidth', trafficForm.tx_bandwidth)
|
||||
if (trafficForm._trafficGB) fd.append('traffic_max', Math.round(trafficForm._trafficGB * 1024)) // GB → Mb
|
||||
if (trafficForm.traffic_exhausted_rx_mbps > 0) fd.append('traffic_exhausted_rx_mbps', trafficForm.traffic_exhausted_rx_mbps)
|
||||
if (trafficForm.traffic_exhausted_tx_mbps > 0) fd.append('traffic_exhausted_tx_mbps', trafficForm.traffic_exhausted_tx_mbps)
|
||||
const res = await updateVmTraffic(fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success('带宽修改成功'); trafficDialogVisible.value = false; loadDetail() }
|
||||
else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { actionLoading.value = false }
|
||||
}
|
||||
|
||||
// ---- 流量策略 ----
|
||||
// 测试未通过(接口新增,待联调)
|
||||
const vmTrafficPolicy = ref(null)
|
||||
const vmTrafficPolicyLoading = ref(false)
|
||||
const vmTrafficPolicyVisible = ref(false)
|
||||
const vmAddTrafficVisible = ref(false)
|
||||
const vmAddTrafficType = ref('fixed')
|
||||
const vmTrafficPolicyForm = reactive({ traffic_max_mb: 0, exhausted_rx_mbps: 0, exhausted_tx_mbps: 0 })
|
||||
const vmAddTrafficForm = reactive({ traffic_mb: 1 })
|
||||
|
||||
const loadVmTrafficPolicy = async () => {
|
||||
if (!serviceId.value || !vmId.value) return
|
||||
vmTrafficPolicyLoading.value = true
|
||||
try {
|
||||
const res = await getVmTrafficPolicy({ service_id: serviceId.value, vm_id: vmId.value })
|
||||
if (res?.data?.code === 200) vmTrafficPolicy.value = res.data.data
|
||||
} catch { /* ignore */ } finally { vmTrafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
const openVmTrafficPolicyDialog = () => {
|
||||
Object.assign(vmTrafficPolicyForm, {
|
||||
traffic_max_mb: vmTrafficPolicy.value?.traffic_max_mb || 0,
|
||||
exhausted_rx_mbps: vmTrafficPolicy.value?.exhausted_rx_mbps || 0,
|
||||
exhausted_tx_mbps: vmTrafficPolicy.value?.exhausted_tx_mbps || 0
|
||||
})
|
||||
vmTrafficPolicyVisible.value = true
|
||||
}
|
||||
|
||||
const submitUpdateVmTrafficPolicy = async () => {
|
||||
vmTrafficPolicyLoading.value = true
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('service_id', serviceId.value)
|
||||
fd.append('vm_id', vmId.value)
|
||||
fd.append('traffic_max_mb', vmTrafficPolicyForm.traffic_max_mb)
|
||||
fd.append('exhausted_rx_mbps', vmTrafficPolicyForm.exhausted_rx_mbps)
|
||||
fd.append('exhausted_tx_mbps', vmTrafficPolicyForm.exhausted_tx_mbps)
|
||||
const res = await updateVmTrafficPolicy(fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success('修改成功'); vmTrafficPolicyVisible.value = false; loadVmTrafficPolicy() }
|
||||
else ElMessage.error(extractApiError(res?.data, '修改失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { vmTrafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
const openVmAddTrafficDialog = (type) => {
|
||||
vmAddTrafficType.value = type
|
||||
vmAddTrafficForm.traffic_mb = 1
|
||||
vmAddTrafficVisible.value = true
|
||||
}
|
||||
|
||||
const submitAddVmTraffic = async () => {
|
||||
if (!vmAddTrafficForm.traffic_mb || vmAddTrafficForm.traffic_mb < 1) { ElMessage.warning('请输入有效的流量数量'); return }
|
||||
vmTrafficPolicyLoading.value = true
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('service_id', serviceId.value)
|
||||
fd.append('vm_id', vmId.value)
|
||||
fd.append('traffic_mb', vmAddTrafficForm.traffic_mb)
|
||||
const apiFn = vmAddTrafficType.value === 'fixed' ? addVmFixedTraffic : addVmTemporaryTraffic
|
||||
const res = await apiFn(fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success('操作成功'); vmAddTrafficVisible.value = false; loadVmTrafficPolicy() }
|
||||
else ElMessage.error(extractApiError(res?.data, '操作失败'))
|
||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '操作失败')) } finally { vmTrafficPolicyLoading.value = false }
|
||||
}
|
||||
|
||||
// ---- 迁移虚拟机 ----
|
||||
const migrateDialogVisible = ref(false)
|
||||
const migrateOptionsLoading = ref(false)
|
||||
@@ -3474,6 +3619,7 @@ const triggerTabLoad = (tab) => {
|
||||
if (tab === 'backup') { loadBackups(); loadBackupQuota() }
|
||||
if (tab === 'userNetworking') loadVmNetworkingList()
|
||||
if (tab === 'security') loadSgLockInfo()
|
||||
if (tab === 'vmTrafficPolicy') loadVmTrafficPolicy()
|
||||
}
|
||||
|
||||
// 请求安全组详情补充 lock 字段
|
||||
|
||||
@@ -176,6 +176,16 @@
|
||||
<el-form-item label="上行带宽">
|
||||
<el-input-number v-model="createForm.tx_bandwidth" :min="0" controls-position="right" /><span class="tk-res-unit">Mbps</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="流量上限">
|
||||
<el-input-number v-model="createForm.traffic_max" :min="0" controls-position="right" /><span class="tk-res-unit">MB</span>
|
||||
<div style="font-size:12px;color:#909399;margin-top:2px">0 表示不限流量</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽下行限速">
|
||||
<el-input-number v-model="createForm.traffic_exhausted_rx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="耗尽上行限速">
|
||||
<el-input-number v-model="createForm.traffic_exhausted_tx_mbps" :min="0" :precision="2" controls-position="right" /><span class="tk-res-unit">Mbps(0 不限)</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="额外数据卷">
|
||||
<div style="display:flex;align-items:center;gap:6px">
|
||||
@@ -616,7 +626,8 @@ const createForm = reactive({
|
||||
name: '', host_id: null, image_id: 0, vcpu: 0, memory: 0,
|
||||
system_size: 0, rx_bandwidth: 0, tx_bandwidth: 0, data_volume_size: 0,
|
||||
host_group_id: null, user_id: 0, ipv4_num: 0, ipv6_num: 0, network_ids: [],
|
||||
_imageName: '', _groupName: '', _userName: ''
|
||||
_imageName: '', _groupName: '', _userName: '',
|
||||
traffic_max: 0, traffic_exhausted_rx_mbps: 0, traffic_exhausted_tx_mbps: 0
|
||||
})
|
||||
|
||||
const createRules = {
|
||||
@@ -802,6 +813,9 @@ const submitCreate = () => {
|
||||
if (createForm.ipv4_num) fd.append('ipv4_num', createForm.ipv4_num)
|
||||
if (createForm.ipv6_num) fd.append('ipv6_num', createForm.ipv6_num)
|
||||
} else createForm.network_ids.forEach(id => fd.append('network_ids', id))
|
||||
if (createForm.traffic_max > 0) fd.append('traffic_max', createForm.traffic_max)
|
||||
if (createForm.traffic_exhausted_rx_mbps > 0) fd.append('traffic_exhausted_rx_mbps', createForm.traffic_exhausted_rx_mbps)
|
||||
if (createForm.traffic_exhausted_tx_mbps > 0) fd.append('traffic_exhausted_tx_mbps', createForm.traffic_exhausted_tx_mbps)
|
||||
const res = await createVm(fd)
|
||||
if (res?.data?.code === 200) { ElMessage.success('创建成功'); createDialogVisible.value = false; loadList() }
|
||||
else ElMessage.error(extractApiError(res?.data, '创建失败'))
|
||||
|
||||
Reference in New Issue
Block a user