""" Process list viewer — shows running server processes with kill capability. Auto-refresh support with configurable interval. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox, QMessageBox, ) from PyQt6.QtCore import Qt, pyqtSignal, QTimer from app.utils.worker import SchemaWorker class ProcessListPanel(QWidget): status_message = pyqtSignal(str) def __init__(self, driver, connection_name: str = "", parent=None): super().__init__(parent) self._driver = driver self._connection_name = connection_name self._refresh_timer = QTimer(self) self._refresh_timer.timeout.connect(self._refresh) self._build_ui() self._refresh() # ── UI ──────────────────────────────────────────────────────────────────── def _build_ui(self): root = QVBoxLayout(self) root.setContentsMargins(0, 0, 0, 0) # Header bar hdr = QHBoxLayout() hdr.setContentsMargins(8, 6, 8, 4) title = QLabel(f"⚙️ Process List — {self._connection_name}") title.setObjectName("structureTitle") f = title.font() f.setBold(True) f.setPointSize(12) title.setFont(f) self._auto_cb = QCheckBox("Auto-refresh (5s)") self._auto_cb.toggled.connect(self._toggle_auto_refresh) self._refresh_btn = QPushButton("🔄 Refresh") self._refresh_btn.setFixedWidth(90) self._refresh_btn.clicked.connect(self._refresh) self._kill_btn = QPushButton("🛑 Kill Process") self._kill_btn.setObjectName("deleteBtn") self._kill_btn.setEnabled(False) self._kill_btn.clicked.connect(self._kill_selected) hdr.addWidget(title) hdr.addStretch() hdr.addWidget(self._auto_cb) hdr.addWidget(self._refresh_btn) hdr.addWidget(self._kill_btn) root.addLayout(hdr) # Process table self._table = QTableWidget(0, 0) self._table.setAlternatingRowColors(True) self._table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows) self._table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self._table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeMode.ResizeToContents ) self._table.horizontalHeader().setStretchLastSection(True) self._table.verticalHeader().setDefaultSectionSize(24) self._table.verticalHeader().setVisible(False) self._table.itemSelectionChanged.connect(self._on_selection_changed) root.addWidget(self._table) # ── Data loading ────────────────────────────────────────────────────────── def _refresh(self): self._refresh_btn.setEnabled(False) w = SchemaWorker(self._driver.get_process_list, parent=self) w.result.connect(self._populate) w.error.connect(self._on_error) w.finished.connect(lambda: self._refresh_btn.setEnabled(True)) w.start() def _populate(self, data): cols, rows = data self._table.setColumnCount(len(cols)) self._table.setHorizontalHeaderLabels([c.upper() for c in cols]) self._table.setRowCount(0) for row in rows: r = self._table.rowCount() self._table.insertRow(r) for c, val in enumerate(row): item = QTableWidgetItem("" if val is None else str(val)) item.setTextAlignment( Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft ) self._table.setItem(r, c, item) self.status_message.emit( f"Process list: {len(rows)} process(es)" ) def _on_error(self, msg: str): self._table.setRowCount(1) self._table.setColumnCount(1) self._table.setHorizontalHeaderLabels(["Error"]) self._table.setItem(0, 0, QTableWidgetItem(msg)) self.status_message.emit(f"Process list error: {msg}") # ── Kill ────────────────────────────────────────────────────────────────── def _on_selection_changed(self): self._kill_btn.setEnabled(bool(self._table.selectedItems())) def _kill_selected(self): row = self._table.currentRow() if row < 0: return # Process ID is always the first column pid_item = self._table.item(row, 0) if not pid_item or not pid_item.text(): return try: pid = int(pid_item.text()) except ValueError: self.status_message.emit("Could not determine process ID.") return btn = QMessageBox.warning( self, "Kill Process", f"Kill process {pid}?\n\nThis will immediately terminate the running query.", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No, ) if btn != QMessageBox.StandardButton.Yes: return w = SchemaWorker(self._driver.kill_process, pid, parent=self) w.result.connect(lambda _: ( self.status_message.emit(f"Process {pid} killed."), self._refresh(), )) w.error.connect( lambda e: QMessageBox.warning(self, "Kill Error", f"Could not kill process {pid}:\n{e}") ) w.start() # ── Auto-refresh ────────────────────────────────────────────────────────── def _toggle_auto_refresh(self, checked: bool): if checked: self._refresh_timer.start(5000) else: self._refresh_timer.stop() def closeEvent(self, event): self._refresh_timer.stop() super().closeEvent(event)