Files
arrr-erp/app/modules/documents/local_agent_runtime/ERPAgentDashboard.pyw
T
2026-08-22 19:36:51 +05:30

195 lines
4.7 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()