Files
virtualbox_client/scripts/add_script_tab.py
T
2026-05-10 10:56:52 +08:00

276 lines
11 KiB
Python

# This script adds ScriptWorker and a script execution tab to gui.py
import os
gui_path = "src/virtualbox_client/gui.py"
with open(gui_path, "r", encoding="utf-8") as f:
content = f.read()
# 1. Add ScriptWorker before WorkspaceWorker
old_worker = "class WorkspaceWorker(QObject):"
new_worker = """class ScriptWorker(QObject):
log_signal = Signal(str, str)
finished_signal = Signal(bool)
error_signal = Signal(str)
def __init__(self, config: dict, local_path: str):
super().__init__()
self.config = config
self.local_path = local_path
self.cancel_event = threading.Event()
def cancel(self):
self.cancel_event.set()
def run(self):
ssh_only = self.config.get("ssh_only", False)
_com_initialized = False
if not ssh_only:
try:
import ctypes
ctypes.windll.ole32.CoInitializeEx(None, 0)
_com_initialized = True
except Exception:
pass
try:
if ssh_only:
from virtualbox_client.utils.ssh_manager import SSHManager
vm = SSHManager(
self.config["ssh_host"], self.config["ssh_port"],
self.config["ssh_user"], self.config["ssh_pass"],
)
else:
from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager
vm = VBoxSSHManager(
self.config["vm_name"], self.config["ssh_host"],
self.config["ssh_port"], self.config["ssh_user"],
self.config["ssh_pass"],
)
vm.set_cancel_event(self.cancel_event)
if self.cancel_event.is_set():
return
import builtins
original_print = print
def custom_print(*args, **kwargs):
msg = " ".join(str(a) for a in args)
self.log_signal.emit(msg, "info")
original_print(*args, **kwargs)
builtins.print = custom_print
try:
import os as _os
script_name = _os.path.basename(self.local_path)
remote_path = "/tmp/" + script_name
log_path = "/tmp/" + script_name + ".log"
local_log_path = script_name + ".log"
self.log_signal.emit("[1/3] Upload: " + self.local_path + " -> " + remote_path, "info")
if not vm.upload_file(self.local_path, remote_path):
self.log_signal.emit("Upload failed", "error")
return
self.log_signal.emit("[2/3] Execute: " + remote_path, "info")
cmd = (
"chmod +x " + remote_path + " && "
"echo '" + self.config["ssh_pass"] + "' | sudo -S " + remote_path + " 2>&1 | "
"tee " + log_path
)
out, err, code = vm.exec_command(cmd, get_pty=False)
self.log_signal.emit("Exit code: " + str(code), "info")
self.log_signal.emit("[3/3] Download log: " + log_path + " -> " + local_log_path, "info")
if vm.download_file(log_path, local_log_path):
self.log_signal.emit("Log saved: " + local_log_path, "success")
else:
self.log_signal.emit("Tee log download failed, saving terminal output", "warning")
if out:
with open(local_log_path, "w") as f:
f.write(out)
self.log_signal.emit("Output saved: " + local_log_path, "success")
vm.exec_command("rm -f \\"" + remote_path + "\\" \\"" + log_path + "\\"")
self.log_signal.emit("Remote temp files cleaned", "info")
self.log_signal.emit("Script execution complete!", "success")
self.finished_signal.emit(True)
finally:
builtins.print = original_print
except ImportError as e:
self.log_signal.emit("Missing dependency: " + str(e), "error")
self.error_signal.emit(str(e))
except Exception as e:
self.log_signal.emit("Script error: " + str(e), "error")
self.error_signal.emit(str(e))
finally:
if _com_initialized:
try:
import ctypes
ctypes.windll.ole32.CoUninitialize()
except Exception:
pass
""" + old_worker
content = content.replace(old_worker, new_worker)
# 2. Add script tab after file transfer tab
old_tab_end = ' file_layout.addStretch()\n self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93")'
new_tab_end = ''' file_layout.addStretch()
self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93")
# ====== Script Execution Tab ======
script_tab = QWidget()
script_layout = QVBoxLayout(script_tab)
script_layout.setContentsMargins(8, 8, 8, 8)
script_group = QGroupBox("Upload and Execute Script")
script_group_layout = QVBoxLayout(script_group)
script_path_layout = QHBoxLayout()
self.script_path_edit = QLineEdit(placeholderText="Select local .sh script file...")
script_path_layout.addWidget(self.script_path_edit)
self.script_browse_btn = QPushButton("Browse")
self.script_browse_btn.setFixedWidth(80)
self.script_browse_btn.clicked.connect(self._browse_script)
script_path_layout.addWidget(self.script_browse_btn)
script_group_layout.addWidget(QLabel("Local Script:"))
script_group_layout.addLayout(script_path_layout)
self.run_script_btn = QPushButton("Upload and Execute Script")
self.run_script_btn.setStyleSheet("""
QPushButton { background-color: #8e44ad; color: white; padding: 10px 30px;
font-size: 14px; font-weight: bold; border: none; border-radius: 6px; }
QPushButton:hover { background-color: #9b59b6; }
QPushButton:disabled { background-color: #555; color: #999; }
""")
self.run_script_btn.clicked.connect(self._run_script)
script_group_layout.addWidget(self.run_script_btn)
script_layout.addWidget(script_group)
desc_group = QGroupBox("How it works")
desc_layout = QVBoxLayout(desc_group)
desc_label = QLabel(
"1. Select a local .sh script file\\n"
"2. Click button to auto:\\n"
" Upload script to /tmp/\\n"
" Execute with sudo\\n"
" Download execution log\\n"
" Clean remote temp files"
)
desc_label.setStyleSheet("color: #aaa; font-size: 12px; line-height: 1.6;")
desc_layout.addWidget(desc_label)
script_layout.addWidget(desc_group)
script_layout.addStretch()
self.tab_widget.addTab(script_tab, "Script")'''
content = content.replace(old_tab_end, new_tab_end)
# 3. Update _set_ui_running
old_running = """ def _set_ui_running(self, running: bool):
self.run_btn.setEnabled(not running)
self.cancel_btn.setEnabled(running)
self.task_input.setEnabled(not running)
if self._config_panel:
self._config_panel.set_enabled(not running)"""
new_running = """ def _set_ui_running(self, running: bool):
self.run_btn.setEnabled(not running)
self.cancel_btn.setEnabled(running)
self.task_input.setEnabled(not running)
self.run_script_btn.setEnabled(not running)
self.script_browse_btn.setEnabled(not running)
self.script_path_edit.setEnabled(not running)
if self._config_panel:
self._config_panel.set_enabled(not running)"""
content = content.replace(old_running, new_running)
# 4. Add methods before MainWindow
old_mw = "class MainWindow(QMainWindow):"
new_mw = """ _script_thread = None
_script_worker = None
def _cleanup_script(self):
if self._script_thread:
try:
self._script_thread.finished.disconnect()
except (TypeError, RuntimeError):
pass
if self._script_thread.isRunning():
self._script_thread.quit()
if not self._script_thread.wait(5000):
self._script_thread.terminate()
self._script_thread.wait(3000)
self._script_thread = None
if self._script_worker:
try:
self._script_worker.log_signal.disconnect()
self._script_worker.finished_signal.disconnect()
self._script_worker.error_signal.disconnect()
except (TypeError, RuntimeError):
pass
self._script_worker = None
def _browse_script(self):
file_path, _ = QFileDialog.getOpenFileName(
self, "Select script", "", "Shell scripts (*.sh);;All files (*)"
)
if file_path:
self.script_path_edit.setText(file_path)
def _run_script(self):
if not self._config_panel or not self._config_panel.validate():
return
local_path = self.script_path_edit.text().strip()
if not local_path:
QMessageBox.warning(self, "Hint", "Please select a script file")
return
if not os.path.isfile(local_path):
QMessageBox.warning(self, "Hint", "File not found: " + local_path)
return
self._cleanup_script()
self._set_ui_running(True)
self.tab_widget.setCurrentIndex(0)
self.log_panel.clear_log()
config = self._config_panel.get_config()
self._script_worker = ScriptWorker(config, local_path)
self._script_thread = QThread(self)
self._script_worker.moveToThread(self._script_thread)
self._script_worker.log_signal.connect(self.log_panel.append_log)
self._script_worker.finished_signal.connect(self._on_script_finished)
self._script_worker.error_signal.connect(self._on_script_error)
self._script_thread.started.connect(self._script_worker.run)
self._script_thread.finished.connect(self._cleanup_script)
self._script_thread.start()
def _on_script_finished(self, success: bool):
self._set_ui_running(False)
if success:
self.log_panel.append_log("Script execution completed", "success")
def _on_script_error(self, error_msg: str):
self._set_ui_running(False)
self.log_panel.append_log("Script error: " + error_msg, "error")
""" + old_mw
content = content.replace(old_mw, new_mw)
with open(gui_path, "w", encoding="utf-8") as f:
f.write(content)
print("Done")