Files
admin 8ccccf8f52 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版本兼容性修复
2026-05-07 12:41:10 +08:00

117 lines
3.8 KiB
Python

"""主机管理路由"""
from fastapi import APIRouter, HTTPException
from app.hosts import (
list_hosts, get_host, add_host, remove_host,
test_connection, update_host_status, HostCreate,
)
from app.libvirt_conn import conn_pool
import time
router = APIRouter()
@router.get("/list")
async def api_list_hosts():
"""列出所有已注册主机"""
hosts = list_hosts()
result = []
for h in hosts:
info = h.model_dump()
# 尝试获取实时状态
try:
mgr = conn_pool.get(h.id)
conn = mgr.conn
alive = conn.isAlive()
info["status"] = "online" if alive else "offline"
if alive:
host_data = conn.getInfo()
info["cpu_cores"] = host_data[2]
info["memory_mb"] = host_data[1] # getInfo()[1] 已经是 MiB 单位
info["hostname"] = conn.getHostname()
# 虚拟机数
domains = conn.listAllDomains(0)
info["vm_total"] = len(domains)
info["vm_running"] = sum(1 for d in domains if d.isActive())
update_host_status(h.id, info["status"])
except Exception:
info["status"] = "offline"
update_host_status(h.id, "offline")
result.append(info)
return {"hosts": result, "total": len(result)}
@router.get("/detail/{host_id}")
async def api_get_host(host_id: str):
"""获取单台主机详情"""
host = get_host(host_id)
if not host:
raise HTTPException(status_code=404, detail=f"主机 '{host_id}' 不存在")
info = host.model_dump()
try:
mgr = conn_pool.get(host_id)
info["host_info"] = mgr.get_host_info()
info["status"] = "online"
# 虚拟机数
domains = mgr.conn.listAllDomains(0)
info["vm_total"] = len(domains)
info["vm_running"] = sum(1 for d in domains if d.isActive())
update_host_status(host_id, "online")
except Exception as e:
info["status"] = "offline"
info["error"] = str(e)
update_host_status(host_id, "offline")
return info
@router.post("/add")
async def api_add_host(req: HostCreate):
"""添加新主机"""
# 先测试连接
result = test_connection(req.uri)
if not result["success"]:
raise HTTPException(status_code=400, detail=f"连接测试失败: {result['error']}")
host = add_host(req)
# 更新状态为在线
update_host_status(host.id, "online")
return {"message": f"主机 '{host.name}' 添加成功", "host": host.model_dump()}
@router.delete("/delete/{host_id}")
async def api_delete_host(host_id: str):
"""删除主机"""
if host_id == "local":
raise HTTPException(status_code=400, detail="不能删除本机")
if not remove_host(host_id):
raise HTTPException(status_code=404, detail=f"主机 '{host_id}' 不存在")
conn_pool.remove(host_id)
return {"message": f"主机 '{host_id}' 已删除"}
@router.post("/test")
async def api_test_connection(req: HostCreate):
"""测试连接(不添加主机)"""
result = test_connection(req.uri)
return result
@router.post("/refresh/{host_id}")
async def api_refresh_host(host_id: str):
"""刷新主机状态"""
host = get_host(host_id)
if not host:
raise HTTPException(status_code=404, detail=f"主机 '{host_id}' 不存在")
try:
# 重新建立连接
conn_pool.remove(host_id)
mgr = conn_pool.get(host_id)
conn = mgr.conn
alive = conn.isAlive()
status = "online" if alive else "offline"
update_host_status(host_id, status)
return {"status": status, "hostname": conn.getHostname()}
except Exception as e:
update_host_status(host_id, "offline")
conn_pool.remove(host_id)
return {"status": "offline", "error": str(e)}