276 lines
11 KiB
Python
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")
|