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:
@@ -0,0 +1,91 @@
|
||||
"""Simple static file server for the KVM frontend with API proxy"""
|
||||
import http.server
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
import os
|
||||
import json
|
||||
|
||||
PORT = 8006
|
||||
DIST_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dist")
|
||||
API_BASE = "http://127.0.0.1:8004"
|
||||
|
||||
|
||||
class KVMHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory=DIST_DIR, **kwargs)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/api/"):
|
||||
self._proxy("GET")
|
||||
elif self.path == "/" or self.path == "":
|
||||
self.path = "/index.html"
|
||||
super().do_GET()
|
||||
else:
|
||||
# SPA fallback: if file doesn't exist, serve index.html
|
||||
file_path = os.path.join(DIST_DIR, self.path.lstrip("/"))
|
||||
if os.path.isfile(file_path):
|
||||
super().do_GET()
|
||||
else:
|
||||
self.path = "/index.html"
|
||||
super().do_GET()
|
||||
|
||||
def do_POST(self):
|
||||
if self.path.startswith("/api/"):
|
||||
self._proxy("POST")
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_PUT(self):
|
||||
if self.path.startswith("/api/"):
|
||||
self._proxy("PUT")
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_DELETE(self):
|
||||
if self.path.startswith("/api/"):
|
||||
self._proxy("DELETE")
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def _proxy(self, method):
|
||||
"""Proxy API requests to backend"""
|
||||
content_length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(content_length) if content_length > 0 else None
|
||||
|
||||
url = f"{API_BASE}{self.path}"
|
||||
req = urllib.request.Request(url, data=body, method=method)
|
||||
|
||||
# Forward headers
|
||||
for key in ["Content-Type", "Authorization"]:
|
||||
if key in self.headers:
|
||||
req.add_header(key, self.headers[key])
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
resp_body = resp.read()
|
||||
self.send_response(resp.status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(resp_body)
|
||||
except urllib.error.HTTPError as e:
|
||||
resp_body = e.read()
|
||||
self.send_response(e.code)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(resp_body)
|
||||
except Exception as e:
|
||||
self.send_response(502)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps({"error": str(e)}).encode())
|
||||
|
||||
def log_message(self, format, *args):
|
||||
pass # Suppress logs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
server = http.server.HTTPServer(("0.0.0.0", PORT), KVMHandler)
|
||||
print(f"KVM Frontend serving on http://0.0.0.0:{PORT}")
|
||||
server.serve_forever()
|
||||
Reference in New Issue
Block a user