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 -13
View File
@@ -1,10 +1,10 @@
"""快照管理路由"""
from fastapi import APIRouter, HTTPException
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional
from lxml import etree
from app.libvirt_conn import libvirt_conn
from app.libvirt_conn import conn_pool
import libvirt
router = APIRouter()
@@ -16,9 +16,9 @@ class SnapshotCreate(BaseModel):
@router.get("/list/{vm_name}")
async def list_snapshots(vm_name: str):
async def list_snapshots(vm_name: str, host_id: str = Query("local")):
"""列出虚拟机的所有快照"""
conn = libvirt_conn.conn
conn = conn_pool.get_conn(host_id)
try:
dom = conn.lookupByName(vm_name)
except libvirt.libvirtError:
@@ -40,15 +40,15 @@ async def list_snapshots(vm_name: str):
"is_current": snap.isCurrent() == 1,
})
except libvirt.libvirtError:
pass # 没有快照
pass
return {"vm": vm_name, "snapshots": snapshots, "total": len(snapshots)}
@router.post("/create/{vm_name}")
async def create_snapshot(vm_name: str, snap: SnapshotCreate):
async def create_snapshot(vm_name: str, snap: SnapshotCreate, host_id: str = Query("local")):
"""创建快照"""
with libvirt_conn.get_rw() as rw_conn:
with conn_pool.get_rw(host_id) as rw_conn:
try:
dom = rw_conn.lookupByName(vm_name)
except libvirt.libvirtError:
@@ -68,9 +68,9 @@ async def create_snapshot(vm_name: str, snap: SnapshotCreate):
@router.post("/revert/{vm_name}/{snap_name}")
async def revert_snapshot(vm_name: str, snap_name: str):
async def revert_snapshot(vm_name: str, snap_name: str, host_id: str = Query("local")):
"""恢复快照"""
with libvirt_conn.get_rw() as rw_conn:
with conn_pool.get_rw(host_id) as rw_conn:
try:
dom = rw_conn.lookupByName(vm_name)
snap = dom.snapshotLookupByName(snap_name)
@@ -85,9 +85,9 @@ async def revert_snapshot(vm_name: str, snap_name: str):
@router.delete("/delete/{vm_name}/{snap_name}")
async def delete_snapshot(vm_name: str, snap_name: str):
async def delete_snapshot(vm_name: str, snap_name: str, host_id: str = Query("local")):
"""删除快照"""
with libvirt_conn.get_rw() as rw_conn:
with conn_pool.get_rw(host_id) as rw_conn:
try:
dom = rw_conn.lookupByName(vm_name)
snap = dom.snapshotLookupByName(snap_name)
@@ -102,9 +102,9 @@ async def delete_snapshot(vm_name: str, snap_name: str):
@router.get("/detail/{vm_name}/{snap_name}")
async def get_snapshot_detail(vm_name: str, snap_name: str):
async def get_snapshot_detail(vm_name: str, snap_name: str, host_id: str = Query("local")):
"""获取快照详情"""
conn = libvirt_conn.conn
conn = conn_pool.get_conn(host_id)
try:
dom = conn.lookupByName(vm_name)
snap = dom.snapshotLookupByName(snap_name)