上传文件至 python
python源码位于此文件夹
This commit is contained in:
185
python/app.py
Normal file
185
python/app.py
Normal file
@@ -0,0 +1,185 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user