177 lines
4.6 KiB
Python
177 lines
4.6 KiB
Python
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
|
|
if os.name == "nt":
|
|
try:
|
|
import ctypes
|
|
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
STILL_ACTIVE = 259
|
|
kernel32 = ctypes.windll.kernel32
|
|
handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, int(pid))
|
|
if not handle:
|
|
return False
|
|
try:
|
|
exit_code = ctypes.c_ulong()
|
|
if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)):
|
|
return False
|
|
return exit_code.value == STILL_ACTIVE
|
|
finally:
|
|
kernel32.CloseHandle(handle)
|
|
except Exception:
|
|
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()
|