Add persistent desktop supervisor for ERP Local Agent
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
from tkinter import Tk, messagebox
|
||||
|
||||
|
||||
INSTALL_ROOT = Path(__file__).resolve().parent
|
||||
DATA_DIR = INSTALL_ROOT / "data"
|
||||
SUPERVISOR_LOCK = DATA_DIR / "supervisor.lock"
|
||||
SUPERVISOR_SCRIPT = INSTALL_ROOT / "ERPAgentSupervisor.pyw"
|
||||
URL = "http://127.0.0.1:8788"
|
||||
|
||||
|
||||
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 _pid_alive(pid: int) -> bool:
|
||||
if pid <= 0:
|
||||
return False
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def _supervisor_running() -> bool:
|
||||
try:
|
||||
if not SUPERVISOR_LOCK.exists():
|
||||
return False
|
||||
return _pid_alive(int(SUPERVISOR_LOCK.read_text(encoding="utf-8").strip()))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
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 _ensure_supervisor() -> None:
|
||||
if _supervisor_running():
|
||||
return
|
||||
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,
|
||||
)
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
if _supervisor_running():
|
||||
return
|
||||
time.sleep(0.5)
|
||||
raise RuntimeError("ERP Local Agent supervisor did not start.")
|
||||
|
||||
|
||||
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 _wait_ready(seconds: int = 75) -> bool:
|
||||
deadline = time.time() + seconds
|
||||
while time.time() < deadline:
|
||||
if _ready():
|
||||
return True
|
||||
time.sleep(0.75)
|
||||
return False
|
||||
|
||||
|
||||
def main() -> None:
|
||||
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 supervisor is running, but the dashboard did not become ready.\n\n"
|
||||
f"Check logs under:\n{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.5)
|
||||
except Exception:
|
||||
time.sleep(2)
|
||||
|
||||
webview.start(monitor)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user