feat: pre-computed state store and async ACME issuance (fixes timeout mismatch)
- Add lib/state.py: in-memory state store with subsystem collectors (firewall, dnsmasq, nginx, acme, wireguard) - Refactor all handlers: read from state on GET, call refresh_state() after mutations instead of invoking subprocesses per request - daemon/server.py: add refresh_state(), /status/all, /status/refresh; populate state at startup - webui/api/certs.py: async step-by-step ACME issuance (validate, issue with request_id, poll status) replacing blocking endpoint - webui/server.py: render pages from state instead of direct lib calls - Update templates, JS for async cert issuance with polling UI - Update tests for state-based mocking; add test_state.py - Fix SIM105 lint issue (contextlib.suppress) - Add TODO.md with certificate issuance issue tracking Resolves: WebUI 30s timeout freeze during cert issuance (Problem 1)
This commit is contained in:
+8
-10
@@ -12,11 +12,9 @@ from lib.common import run_proc
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PROJECT_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
APP_LOG_FILE = PROJECT_DIR / "data" / "logs" / "vacuum-wall.log"
|
||||
_APP_LOG_FILE = PROJECT_DIR / "data" / "logs" / "vacuum-wall.log"
|
||||
_MAX_LINES = 200
|
||||
|
||||
_LOG_TAGS = {"logs"}
|
||||
|
||||
|
||||
def _tail_file(path: str, n: int = _MAX_LINES, sudo: bool = False) -> str:
|
||||
try:
|
||||
@@ -36,7 +34,7 @@ def _tail_file(path: str, n: int = _MAX_LINES, sudo: bool = False) -> str:
|
||||
def _sudo_journalctl(unit: str, n: int = _MAX_LINES) -> str:
|
||||
try:
|
||||
result = run_proc(
|
||||
["journalctl", "-u", unit, "--no-pager", "-n", str(n)],
|
||||
["journalctl", "--unit=" + unit, "-n", str(n)],
|
||||
sudo=True,
|
||||
check=False,
|
||||
timeout=10,
|
||||
@@ -47,26 +45,26 @@ def _sudo_journalctl(unit: str, n: int = _MAX_LINES) -> str:
|
||||
return f"(error reading journal: {exc})\n"
|
||||
|
||||
|
||||
@registry.register("GET", "/logs/journal", cache_tags=_LOG_TAGS)
|
||||
@registry.register("GET", "/logs/journal")
|
||||
def journal(_request, _body) -> str:
|
||||
return _sudo_journalctl("vacuum-wall")
|
||||
|
||||
|
||||
@registry.register("GET", "/logs/nginx/access", cache_tags=_LOG_TAGS)
|
||||
@registry.register("GET", "/logs/nginx/access")
|
||||
def nginx_access(_request, _body) -> str:
|
||||
return _tail_file("/var/log/nginx/access.log", sudo=True)
|
||||
|
||||
|
||||
@registry.register("GET", "/logs/nginx/error", cache_tags=_LOG_TAGS)
|
||||
@registry.register("GET", "/logs/nginx/error")
|
||||
def nginx_error(_request, _body) -> str:
|
||||
return _tail_file("/var/log/nginx/error.log", sudo=True)
|
||||
|
||||
|
||||
@registry.register("GET", "/logs/dnsmasq", cache_tags=_LOG_TAGS)
|
||||
@registry.register("GET", "/logs/dnsmasq")
|
||||
def dnsmasq_log(_request, _body) -> str:
|
||||
return _sudo_journalctl("dnsmasq")
|
||||
|
||||
|
||||
@registry.register("GET", "/logs/app", cache_tags=_LOG_TAGS)
|
||||
@registry.register("GET", "/logs/app")
|
||||
def app_log(_request, _body) -> str:
|
||||
return _tail_file(str(APP_LOG_FILE))
|
||||
return _tail_file(str(_APP_LOG_FILE))
|
||||
|
||||
Reference in New Issue
Block a user