186 lines
6.0 KiB
Python
186 lines
6.0 KiB
Python
import tkinter as tk
|
||
from tkinter import scrolledtext
|
||
import logging
|
||
import pystray
|
||
from PIL import Image, ImageDraw
|
||
from pystray import MenuItem as item
|
||
import threading
|
||
import subprocess
|
||
import json
|
||
from flask import Flask, render_template, request, jsonify
|
||
|
||
app = Flask(__name__)
|
||
# 配置日志记录
|
||
logging.basicConfig(
|
||
filename='app.log',
|
||
level=logging.DEBUG,
|
||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||
)
|
||
# 托盘应用类
|
||
class TrayApp:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("虚拟机管理控制面板")
|
||
self.root.geometry("300x200")
|
||
|
||
# 左侧控制面板
|
||
control_frame = tk.Frame(self.root, width=300, bg="lightgray")
|
||
control_frame.pack(side="left", fill="y")
|
||
|
||
# 最小化按钮
|
||
minimize_button = tk.Button(control_frame, text="最小化到托盘", command=self.hide_window)
|
||
minimize_button.pack(pady=20)
|
||
|
||
# 启动 Flask 服务器的按钮
|
||
start_server_button = tk.Button(control_frame, text="启动 Flask 服务器", command=self.start_flask_server)
|
||
start_server_button.pack(pady=10)
|
||
|
||
# 后台线程运行托盘图标
|
||
self.tray_thread = threading.Thread(target=self.create_tray_icon, daemon=True)
|
||
self.tray_thread.start()
|
||
|
||
def hide_window(self):
|
||
"""隐藏窗口到托盘"""
|
||
self.root.withdraw() # 隐藏主窗口
|
||
|
||
def show_window(self, icon, item):
|
||
"""从托盘显示窗口"""
|
||
self.root.deiconify() # 显示主窗口
|
||
|
||
def exit_app(self, icon, item):
|
||
"""退出应用程序"""
|
||
icon.stop() # 停止托盘图标
|
||
self.root.quit() # 退出Tkinter主循环
|
||
self.root.destroy() # 销毁窗口
|
||
|
||
def create_tray_icon(self):
|
||
"""创建系统托盘图标"""
|
||
image = self.create_image()
|
||
menu = (item('显示', self.show_window), item('退出', self.exit_app))
|
||
tray_icon = pystray.Icon("VM Manager", image, "虚拟机管理", menu)
|
||
tray_icon.run()
|
||
|
||
def create_image(self, width=64, height=64, color1="black", color2="white"):
|
||
"""生成托盘图标"""
|
||
image = Image.new("RGB", (width, height), color1)
|
||
dc = ImageDraw.Draw(image)
|
||
dc.rectangle((width // 2, 0, width, height // 2), fill=color2)
|
||
dc.rectangle((0, height // 2, width // 2, height), fill=color2)
|
||
return image
|
||
|
||
def start_flask_server(self):
|
||
"""启动 Flask 服务器"""
|
||
flask_thread = threading.Thread(target=self.run_flask, daemon=True)
|
||
flask_thread.start()
|
||
|
||
def run_flask(self):
|
||
"""在后台运行 Flask 服务器"""
|
||
app.run(host='0.0.0.0', port=5000)
|
||
|
||
def log_message(self, message):
|
||
"""更新日志到GUI中"""
|
||
self.log_box.config(state="normal")
|
||
self.log_box.insert(tk.END, message + '\n')
|
||
self.log_box.yview(tk.END)
|
||
self.log_box.config(state="disabled")
|
||
|
||
|
||
# Hyper-V 相关操作
|
||
def run_powershell(command):
|
||
"""运行 PowerShell 命令并返回输出"""
|
||
try:
|
||
result = subprocess.run(
|
||
["powershell", "-Command", command],
|
||
capture_output=True, text=True, shell=True
|
||
)
|
||
# 返回标准输出和错误输出合并的结果
|
||
print(result.stdout.strip() + "\n" + result.stderr.strip())
|
||
return result.stdout.strip() + "\n" + result.stderr.strip()
|
||
|
||
except Exception as e:
|
||
print(f"运行 PowerShell 命令时出错: {e}")
|
||
return ""
|
||
|
||
def get_vms():
|
||
"""
|
||
获取所有Hyper-V虚拟机及其状态
|
||
"""
|
||
try:
|
||
command = "Get-VM | Select-Object Name, State | ConvertTo-Json"
|
||
output = run_powershell(command)
|
||
|
||
# 找到JSON开始的部分(第一个 '[' 出现的位置)
|
||
json_start = output.find('[')
|
||
if json_start != -1:
|
||
json_data = output[json_start:] # 从 '[' 开始提取
|
||
vms = json.loads(json_data)
|
||
else:
|
||
raise ValueError("未找到有效的JSON数据")
|
||
|
||
# 如果只有一个VM,转换为列表
|
||
if isinstance(vms, dict):
|
||
vms = [vms]
|
||
return vms
|
||
except subprocess.CalledProcessError as e:
|
||
print("Error fetching VMs:", e)
|
||
return []
|
||
except json.JSONDecodeError as e:
|
||
print("JSON 解析错误:", e)
|
||
print("原始输出:", output)
|
||
return []
|
||
|
||
|
||
@app.route('/')
|
||
def index():
|
||
"""显示虚拟机状态的网页"""
|
||
vms = get_vms()
|
||
return render_template('index.html', vms=vms)
|
||
|
||
|
||
@app.route('/control_vm', methods=['POST'])
|
||
def control_vm():
|
||
"""控制虚拟机的操作(启动、停止等)"""
|
||
try:
|
||
vm_name = request.json['name']
|
||
action = request.json['action']
|
||
|
||
if action == 'start':
|
||
command = f"Start-VM -Name '{vm_name}'"
|
||
elif action == 'stop':
|
||
command = f"Stop-VM -Name '{vm_name}' -Force"
|
||
else:
|
||
return jsonify({'status': 'error', 'message': '无效操作'})
|
||
|
||
output = run_powershell(command)
|
||
print(output)
|
||
if "虚拟机已处于指定的状态" not in output :
|
||
result = "操作成功"
|
||
else:
|
||
result = "操作失败"
|
||
|
||
logging.info(f"虚拟机 {vm_name} 执行操作: {result}")
|
||
return jsonify({'status': 'success', 'message': result})
|
||
|
||
except Exception as e:
|
||
logging.error(f"控制虚拟机时出错: {e}")
|
||
return jsonify({'status': 'error', 'message': '操作失败'})
|
||
|
||
|
||
# 启动 Flask 和 GUI 的主程序
|
||
def gui_thread():
|
||
root = tk.Tk()
|
||
app = TrayApp(root)
|
||
# 自动最小化到托盘
|
||
app.hide_window() # 启动时隐藏窗口
|
||
# 在后台启动 Flask 服务器
|
||
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
||
flask_thread.start()
|
||
root.mainloop()
|
||
|
||
def run_flask():
|
||
"""在后台运行 Flask 服务器"""
|
||
app.run(host='0.0.0.0', port=5050)
|
||
|
||
if __name__ == "__main__":
|
||
gui_thread()
|