Files
proxmox-gui/main_window.py

252 lines
10 KiB
Python

from PySide6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QPushButton, QTableWidget, QTableWidgetItem,
QHeaderView, QLineEdit, QFrame, QMessageBox, QStatusBar, QDialog)
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QFont, QIcon, QAction
from vm_manager import VMManager
from config_dialog import ConfigDialog
from styles import DARK_STYLE, CARD_STYLE
class StatCard(QFrame):
def __init__(self, title: str, parent=None):
super().__init__(parent)
self.setObjectName("stat_card")
self.setStyleSheet(CARD_STYLE)
self.setMinimumSize(150, 100)
layout = QVBoxLayout(self)
layout.setSpacing(5)
layout.setContentsMargins(15, 15, 15, 15)
self.value_label = QLabel("0")
self.value_label.setObjectName("stat_value")
self.value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title_label = QLabel(title)
self.title_label.setObjectName("stat_title")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.value_label)
layout.addWidget(self.title_label)
def set_value(self, value: str):
self.value_label.setText(str(value))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.vm_manager = VMManager()
self.vms = []
self.setup_ui()
def setup_ui(self):
self.setWindowTitle("企业级虚拟化管理平台")
self.setMinimumSize(1200, 800)
self.setStyleSheet(DARK_STYLE)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
main_layout.setSpacing(0)
main_layout.setContentsMargins(20, 20, 20, 20)
title_layout = QHBoxLayout()
title_label = QLabel("企业级虚拟化管理平台")
title_label.setObjectName("title_label")
title_layout.addWidget(title_label)
title_layout.addStretch()
config_btn = QPushButton("配置")
config_btn.setObjectName("config_button")
config_btn.clicked.connect(self.open_config)
title_layout.addWidget(config_btn)
main_layout.addLayout(title_layout)
toolbar_layout = QHBoxLayout()
toolbar_layout.addStretch()
self.connect_btn = QPushButton("连接")
self.connect_btn.setObjectName("connect_button")
self.connect_btn.setFixedSize(120, 45)
self.connect_btn.clicked.connect(self.connect_and_load)
toolbar_layout.addWidget(self.connect_btn)
self.refresh_btn = QPushButton("刷新")
self.refresh_btn.setFixedSize(120, 45)
self.refresh_btn.clicked.connect(self.refresh_vms)
toolbar_layout.addWidget(self.refresh_btn)
main_layout.addLayout(toolbar_layout)
stats_layout = QHBoxLayout()
stats_layout.setSpacing(15)
self.total_card = StatCard("虚拟机总数")
stats_layout.addWidget(self.total_card)
self.running_card = StatCard("运行中")
stats_layout.addWidget(self.running_card)
self.stopped_card = StatCard("已停止")
stats_layout.addWidget(self.stopped_card)
self.paused_card = StatCard("已暂停")
stats_layout.addWidget(self.paused_card)
main_layout.addLayout(stats_layout)
list_section_layout = QVBoxLayout()
list_section_layout.setSpacing(10)
section_label = QLabel("虚拟机列表")
section_label.setObjectName("section_label")
list_section_layout.addWidget(section_label)
search_layout = QHBoxLayout()
self.search_edit = QLineEdit()
self.search_edit.setPlaceholderText("搜索虚拟机...")
self.search_edit.textChanged.connect(self.filter_vms)
search_layout.addWidget(self.search_edit)
list_section_layout.addLayout(search_layout)
main_layout.addLayout(list_section_layout)
self.table = QTableWidget()
self.table.setColumnCount(8)
self.table.verticalHeader().setDefaultSectionSize(100)
self.table.setHorizontalHeaderLabels(["VMID", "名称", "状态", "CPU", "内存", "磁盘", "运行时间", "操作"])
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Fixed)
self.table.horizontalHeader().setSectionResizeMode(7, QHeaderView.ResizeMode.Interactive)
self.table.setColumnWidth(0, 60)
self.table.setColumnWidth(2, 80)
self.table.setColumnWidth(7, 480)
self.table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
self.table.setAlternatingRowColors(True)
main_layout.addWidget(self.table)
self.setStatusBar(QStatusBar())
self.statusBar().setSizeGripEnabled(False)
def open_config(self):
dialog = ConfigDialog(self)
if dialog.exec() == QDialog.Accepted:
config = self.vm_manager.get_config()
self.statusBar().showMessage("配置已更新")
def connect_and_load(self):
config = self.vm_manager.get_config()
if not config.get('host') or not config.get('user') or not config.get('password'):
QMessageBox.warning(self, "警告", "请先配置连接信息!")
self.open_config()
return
self.connect_btn.setEnabled(False)
self.statusBar().showMessage("正在连接...")
if self.vm_manager.connect(
config['host'],
config['user'],
config['password'],
config.get('node', 'pve'),
config.get('verify_ssl', False)
):
self.load_vms()
self.statusBar().showMessage("连接成功")
else:
QMessageBox.critical(self, "错误", "连接失败,请检查配置!")
self.statusBar().showMessage("连接失败")
self.connect_btn.setEnabled(True)
def load_vms(self):
self.vms = self.vm_manager.get_vms()
stats = self.vm_manager.get_vm_stats(self.vms)
self.total_card.set_value(str(stats['total']))
self.running_card.set_value(str(stats['running']))
self.stopped_card.set_value(str(stats['stopped']))
self.paused_card.set_value(str(stats['paused']))
self.update_table(self.vms)
self.statusBar().showMessage(f"已加载 {len(self.vms)} 个虚拟机")
def refresh_vms(self):
if self.vm_manager.is_connected():
self.load_vms()
self.statusBar().showMessage("已刷新")
else:
QMessageBox.warning(self, "警告", "请先连接服务器!")
def filter_vms(self, text: str):
if not text:
self.update_table(self.vms)
else:
filtered = [vm for vm in self.vms
if text.lower() in str(vm.get('vmid', ''))
or text.lower() in vm.get('name', '').lower()]
self.update_table(filtered)
def update_table(self, vms: list):
self.table.setRowCount(0)
for vm in vms:
row = self.table.rowCount()
self.table.insertRow(row)
info = self.vm_manager.get_vm_info(vm)
status = info['status']
self.table.setItem(row, 0, QTableWidgetItem(str(info['vmid'])))
self.table.setItem(row, 1, QTableWidgetItem(info['name']))
status_item = QTableWidgetItem(info['status_text'])
status_item.setForeground(Qt.GlobalColor.green if status == 'running' else
Qt.GlobalColor.red if status == 'stopped' else
Qt.GlobalColor.yellow)
self.table.setItem(row, 2, status_item)
self.table.setItem(row, 3, QTableWidgetItem(info['cpu_percent']))
self.table.setItem(row, 4, QTableWidgetItem(info['memory']))
self.table.setItem(row, 5, QTableWidgetItem(info['disk']))
self.table.setItem(row, 6, QTableWidgetItem(info['uptime']))
action_widget = QWidget()
action_layout = QHBoxLayout(action_widget)
action_layout.setSpacing(5)
action_layout.setContentsMargins(5, 2, 5, 2)
node = self.vm_manager.get_config().get('node', 'pve')
vmid = info['vmid']
start_btn = self.create_action_button("启动", "action_start", vmid, node, 'start', status == 'running')
stop_btn = self.create_action_button("关机", "action_stop", vmid, node, 'shutdown', status != 'running')
restart_btn = self.create_action_button("重启", "action_restart", vmid, node, 'reboot', status != 'running')
force_stop_btn = self.create_action_button("强制停止", "action_stop", vmid, node, 'stop', status != 'running')
suspend_btn = self.create_action_button("挂起", "action_button", vmid, node, 'suspend', status != 'running')
action_layout.addWidget(start_btn)
action_layout.addWidget(stop_btn)
action_layout.addWidget(restart_btn)
action_layout.addWidget(force_stop_btn)
action_layout.addWidget(suspend_btn)
self.table.setCellWidget(row, 7, action_widget)
def create_action_button(self, text: str, obj_name: str, vmid: int, node: str, operation: str, disabled: bool):
btn = QPushButton(text)
btn.setObjectName(obj_name)
btn.setEnabled(not disabled)
btn.clicked.connect(lambda: self.execute_vm_operation(vmid, node, operation))
return btn
def execute_vm_operation(self, vmid: int, node: str, operation: str):
operation_names = {
'start': '启动',
'shutdown': '关机',
'reboot': '重启',
'stop': '强制停止',
'suspend': '挂起'
}
if self.vm_manager._vm_operation(vmid, node, operation):
QMessageBox.information(self, "成功", f"虚拟机 {vmid} {operation_names[operation]}命令已发送!")
self.load_vms()
else:
QMessageBox.critical(self, "错误", f"虚拟机 {vmid} {operation_names[operation]}失败!")