252 lines
10 KiB
Python
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]}失败!")
|