Files
proxmox-gui/main_window.py

252 lines
10 KiB
Python
Raw Permalink Normal View History

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]}失败!")