from __future__ import annotations import atexit import ctypes from ctypes import wintypes import hashlib import os from pathlib import Path import subprocess import time import urllib.request from tkinter import Tk, messagebox INSTALL_ROOT = Path(__file__).resolve().parent SUPERVISOR_SCRIPT = INSTALL_ROOT / "ERPAgentSupervisor.pyw" URL = "http://127.0.0.1:8788" ERROR_ALREADY_EXISTS = 183 def _dashboard_mutex_name() -> str: normalized = str(INSTALL_ROOT.resolve()).casefold().encode("utf-8", errors="ignore") suffix = hashlib.sha256(normalized).hexdigest()[:20] return f"Global\\ARRR_ERP_Local_Agent_DASHBOARD_{suffix}" def _acquire_dashboard_mutex(): if os.name != "nt": return object() kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) kernel32.CreateMutexW.argtypes = (ctypes.c_void_p, wintypes.BOOL, wintypes.LPCWSTR) kernel32.CreateMutexW.restype = wintypes.HANDLE ctypes.set_last_error(0) handle = kernel32.CreateMutexW(None, False, _dashboard_mutex_name()) if not handle: raise ctypes.WinError(ctypes.get_last_error()) if ctypes.get_last_error() == ERROR_ALREADY_EXISTS: kernel32.CloseHandle(handle) return None return handle def _close_dashboard_mutex(handle) -> None: if os.name != "nt" or handle is None or not isinstance(handle, int): return try: ctypes.WinDLL("kernel32", use_last_error=True).CloseHandle(handle) except Exception: pass def _focus_existing_dashboard() -> None: if os.name != "nt": return try: user32 = ctypes.WinDLL("user32", use_last_error=True) EnumWindowsProc = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM) @EnumWindowsProc def callback(hwnd, _lparam): length = user32.GetWindowTextLengthW(hwnd) if length <= 0: return True buf = ctypes.create_unicode_buffer(length + 1) user32.GetWindowTextW(hwnd, buf, length + 1) if "ERP Local Agent Dashboard" in buf.value: user32.ShowWindow(hwnd, 9) user32.SetForegroundWindow(hwnd) return False return True user32.EnumWindows(callback, 0) except Exception: pass def _create_no_window() -> int: return getattr(subprocess, "CREATE_NO_WINDOW", 0) def _popup(title: str, message: str, error: bool = False) -> None: root = Tk() root.withdraw() if error: messagebox.showerror(title, message) else: messagebox.showinfo(title, message) root.destroy() def _pythonw() -> Path: pyw = INSTALL_ROOT / ".venv" / "Scripts" / "pythonw.exe" if pyw.exists(): return pyw py = INSTALL_ROOT / ".venv" / "Scripts" / "python.exe" if py.exists(): return py raise RuntimeError("ERP Local Agent Python environment was not found.") def _ready(timeout: float = 1.5) -> bool: try: with urllib.request.urlopen(URL + "/api/status", timeout=timeout) as response: return int(getattr(response, "status", 200)) < 500 except Exception: return False def _ensure_supervisor() -> None: # If the dashboard is already healthy, never start another supervisor or worker. if _ready(): return started_by_task = False if os.name == "nt": result = subprocess.run( [ "powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", "Start-ScheduledTask -TaskName 'ERP Local Agent' -ErrorAction Stop", ], cwd=str(INSTALL_ROOT), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=_create_no_window(), check=False, ) started_by_task = result.returncode == 0 if not started_by_task: # Recovery fallback. The Supervisor Global mutex makes repeated launches harmless. subprocess.Popen( [str(_pythonw()), str(SUPERVISOR_SCRIPT), "--background"], cwd=str(INSTALL_ROOT), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=_create_no_window(), close_fds=True, ) def _wait_ready(seconds: int = 90) -> bool: deadline = time.time() + seconds while time.time() < deadline: if _ready(): return True time.sleep(0.75) return False def main() -> None: dashboard_mutex = _acquire_dashboard_mutex() if dashboard_mutex is None: _focus_existing_dashboard() return atexit.register(_close_dashboard_mutex, dashboard_mutex) try: _ensure_supervisor() except Exception as exc: _popup("ERP Local Agent", str(exc), error=True) return if not _wait_ready(): _popup( "ERP Local Agent", "The Local Agent did not become ready.\n\n" "Check the 'ERP Local Agent' Scheduled Task and the logs under:\n" f"{INSTALL_ROOT / 'logs'}", error=True, ) return try: import webview except Exception as exc: _popup( "ERP Local Agent Dashboard", "Desktop dashboard dependency could not be loaded.\n\n" f"{exc}\n\nRun the Local Agent updater/installer again.", error=True, ) return window = webview.create_window( "ERP Local Agent Dashboard", URL, width=1180, height=820, min_size=(980, 650), confirm_close=False, ) def monitor(): was_ready = True outage_seen = False while True: try: current = _ready() if was_ready and not current: outage_seen = True if outage_seen and current: try: window.load_url(URL) except Exception: pass outage_seen = False was_ready = current time.sleep(1.0) except Exception: time.sleep(1.5) webview.start(monitor) if __name__ == "__main__": main()