feat: 多主机纳管、用户认证、noVNC控制台、深色主题

主要功能:
- 多主机管理: 支持TCP/SSH方式纳管远程KVM主机
- 用户认证: JWT token认证, 默认admin/admin123
- noVNC控制台: 前端集成noVNC, WebSocket代理VNC连接
- 深色主题: 全局Element Plus深色主题覆盖
- 虚拟机操作: 克隆、迁移、XML编辑、快照管理
- 资源监控: CPU/内存/磁盘IO/网络流量实时监控

Bug修复:
- libvirt getInfo()内存单位修正(MiB非KiB)
- 远程主机VNC 0.0.0.0监听地址连接策略修复
- Dashboard定时器内存泄漏修复
- bcrypt版本兼容性修复
This commit is contained in:
admin
2026-05-07 12:41:10 +08:00
parent fac8ab7470
commit 8ccccf8f52
30 changed files with 1972 additions and 170 deletions
+13 -9
View File
@@ -36,7 +36,7 @@
</el-table-column>
<el-table-column prop="name" label="名称" min-width="150">
<template #default="{ row }">
<el-link type="primary" @click="$router.push(`/vm/${row.name}`)">{{ row.name }}</el-link>
<el-link type="primary" @click="$router.push(`/vm/${row.name}?host_id=${hostId()}`)">{{ row.name }}</el-link>
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
@@ -77,7 +77,7 @@
<el-button v-if="row.state === 'running'" type="danger" size="small"
@click="doAction(row.name, 'force_stop')">强制关</el-button>
<el-button type="primary" size="small"
@click="$router.push(`/vm/${row.name}`)">详情</el-button>
@click="$router.push(`/vm/${row.name}?host_id=${hostId()}`)">详情</el-button>
<el-button type="danger" size="small"
@click="deleteVM(row)">删除</el-button>
</el-button-group>
@@ -133,10 +133,14 @@
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Refresh } from '@element-plus/icons-vue'
import api from '../api'
const route = useRoute()
const hostId = () => route.query.host_id || 'local'
const loading = ref(false)
const creating = ref(false)
const vms = ref([])
@@ -173,7 +177,7 @@ function formatMem(mb) {
async function loadData() {
loading.value = true
try {
const data = await api.get('/vm/list')
const data = await api.get('/vm/list', { params: { host_id: hostId() } })
vms.value = data.vms || []
} catch (e) {}
loading.value = false
@@ -182,9 +186,9 @@ async function loadData() {
async function loadOptions() {
try {
const [pools, nets, isos] = await Promise.all([
api.get('/storage/pools'),
api.get('/network/list'),
api.get('/storage/isos'),
api.get('/storage/pools', { params: { host_id: hostId() } }),
api.get('/network/list', { params: { host_id: hostId() } }),
api.get('/storage/isos', { params: { host_id: hostId() } }),
])
poolOptions.value = (pools.pools || []).map(p => p.name)
networkOptions.value = (nets.networks || []).map(n => n.name)
@@ -196,7 +200,7 @@ async function doAction(name, action) {
const labels = { start: '启动', stop: '关机', force_stop: '强制关机', pause: '暂停', resume: '恢复' }
try {
await ElMessageBox.confirm(`确定要${labels[action]}虚拟机 ${name} 吗?`, '确认', { type: 'info' })
await api.post(`/vm/action/${name}`, { action })
await api.post(`/vm/action/${name}`, { action }, { params: { host_id: hostId() } })
ElMessage.success(`${labels[action]}操作已发送`)
setTimeout(loadData, 2000)
} catch (e) {
@@ -211,7 +215,7 @@ async function createVM() {
}
creating.value = true
try {
await api.post('/vm/create', createForm.value)
await api.post('/vm/create', createForm.value, { params: { host_id: hostId() } })
ElMessage.success('虚拟机创建成功')
showCreateDialog.value = false
loadData()
@@ -228,7 +232,7 @@ async function deleteVM(row) {
'危险操作',
{ type: 'error', confirmButtonText: '确定删除', confirmButtonClass: 'el-button--danger' }
)
await api.delete(`/vm/delete/${row.name}`, { params: { force: row.state === 'running' } })
await api.delete(`/vm/delete/${row.name}`, { params: { force: row.state === 'running', host_id: hostId() } })
ElMessage.success('虚拟机已删除')
loadData()
} catch (e) {