"""Minimal PyQt6 host for dashboard.html (desktop mode). Pattern from Tebra main_window.py. The bot runs as a child process and this window shows its progress. The bot wiring (command, step markers, friendly rules) lives in bot_runner.py, next to this file. Run it with: python dashboard/bridge.py """ import json, sys, threading from pathlib import Path from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal, QUrl, QSettings from PyQt6.QtWidgets import QApplication, QMainWindow from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebChannel import QWebChannel import bot_runner HTML_PATH = Path(__file__).with_name("dashboard.html") def settings(): return QSettings("{{ORG}}", "{{APP_NAME}}") class Bridge(QObject): _emit = pyqtSignal(str) # thread-safe hop to the GUI thread def __init__(self, view): super().__init__() self.view, self._one_run = view, threading.Lock() self._emit.connect(lambda js: self.view.page().runJavaScript(f"window.__pyMsg({js})")) # Restore the saved dark-mode choice on every page load, sample-data mode included. view.loadFinished.connect(lambda _ok: self.send_to_js("apply_dark", {"on": settings().value("dark", False, bool)})) def send_to_js(self, event, data): self._emit.emit(json.dumps(json.dumps({"event": event, "data": data}))) @pyqtSlot() def load_dashboard(self): self.send_to_js("dashboard_data", {"batches": []}) # TODO: read from DB on a worker thread @pyqtSlot() def load_latest_report(self): self.send_to_js("report_data", {"rows": []}) # TODO: read the latest report rows @pyqtSlot() def download_report(self): pass # TODO: export the report rows to .xlsx @pyqtSlot(bool) def set_dark(self, on): settings().setValue("dark", on) @pyqtSlot() def start_automation(self): if self._one_run.acquire(blocking=False): # a second click can't start a second bot threading.Thread(target=self._run, daemon=True).start() def _run(self): try: bot_runner.run(self.send_to_js) finally: self._one_run.release() if __name__ == "__main__": app = QApplication(sys.argv) win, view = QMainWindow(), QWebEngineView() bridge, channel = Bridge(view), QWebChannel() channel.registerObject("pyBridge", bridge) view.page().setWebChannel(channel) view.setUrl(QUrl.fromLocalFile(str(HTML_PATH))) win.setCentralWidget(view); win.resize(1280, 820); win.setWindowTitle("{{APP_NAME}}"); win.show() sys.exit(app.exec())