Portal server mode for Dokploy: Dockerfile, PORTAL_URLS, PORTAL_SERVER

The results Portal in a Linux container (python 3.13-slim + ODBC Driver
18). PORTAL_SERVER=1: no Start / Stop (the bot runs on the Windows PC).
PORTAL_URLS: allowed hosts / origins, Secure cookie when all https.
RESULTS_DB_CONN (a SQL login) goes in Dokploy's environment, never here.
71 test files, 1222 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-27 14:05:14 +05:30
1 parent ce9bdbd974
commit c049cece23
9 files changed
+163 -13

No files matched your search

+30 -8
View File
@@ -58,7 +58,7 @@ from .users import (Accounts, CAN_ADMIN, CAN_RUN, # noqa: E402
IDLE_MINUTES, LOCK_AFTER, LOCK_MINUTES, MANAGES,
Sessions, SqlUserStore, UserError)
from .bot_runner import (AlreadyRunning, BotRunner, # noqa: E402
NotRunning, StopFailed)
NoRunner, NotRunning, StopFailed)
CATEGORIES = ("Pass", "Fail", "Review")
DB_DOWN = "Results database not reachable"
@@ -261,13 +261,32 @@ def _reasons(rows, top=6) -> list:
for (cat, text), n in ordered[:top]]
def portal_settings(urls: str, server: bool) -> dict:
"""create_app's host / origin / cookie / Start settings from
PORTAL_URLS (comma separated, as people type them in the browser) and
PORTAL_SERVER. No URLs: this PC only. A server Portal never starts the
bot; an all-https Portal sends the cookie only over https."""
parts = [urlsplit(u.strip()) for u in (urls or "").split(",")
if u.strip()]
if not parts:
return {"allowed_hosts": list(ALLOWED_HOSTS),
"allowed_origins": list(ALLOWED_ORIGINS),
"secure_cookie": False, "can_start": not server}
return {"allowed_hosts": [u.hostname for u in parts],
"allowed_origins": [f"{u.scheme}://{u.netloc}" for u in parts],
"secure_cookie": all(u.scheme == "https" for u in parts),
"can_start": not server}
def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
allowed_origins=ALLOWED_ORIGINS, logs_dir=None,
screenshots_root=None, users_store=None,
sessions=None) -> FastAPI:
sessions=None, can_start=True,
secure_cookie=False) -> FastAPI:
app = FastAPI(title="eCW Charge Validation — Results",
docs_url=None, redoc_url=None, openapi_url=None)
runner = runner if runner is not None else BotRunner()
if runner is None:
runner = BotRunner() if can_start else NoRunner()
logs_dir = logs_dir if logs_dir is not None else cfg.LOGS
screenshots_root = (screenshots_root if screenshots_root is not None
else cfg.ROOT / "Screenshots")
@@ -379,7 +398,8 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
"mode": "LIVE-WRITE" if cfg.SAVE_CORRECTIONS else "VERIFY",
"run_token": app.state.run_token, "crumbs": crumbs,
"categories": CATEGORIES, "user": user,
"can_run": bool(user and user["role"] in CAN_RUN),
"can_run": bool(can_start and user
and user["role"] in CAN_RUN),
"is_admin": bool(user and user["role"] in CAN_ADMIN),
**context})
@@ -521,7 +541,7 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
@app.post("/api/bot/start")
def bot_start(request: Request):
_check_writer(request)
user = _need(request, CAN_RUN)
user = _need(request, CAN_RUN if can_start else ())
try:
return run_status.summarize(
runner.start(started_by=user["username"]))
@@ -531,7 +551,7 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
@app.post("/api/bot/stop")
def bot_stop(request: Request):
_check_writer(request)
user = _need(request, CAN_RUN)
user = _need(request, CAN_RUN if can_start else ())
try:
return run_status.summarize(
runner.stop(stopped_by=user["username"]))
@@ -576,7 +596,8 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
else _safe_next(next))
response = RedirectResponse(target, status_code=303)
response.set_cookie(COOKIE, sessions.new(user), httponly=True,
samesite="strict", path="/")
samesite="strict", path="/",
secure=secure_cookie)
return response
@app.post("/signout")
@@ -709,4 +730,5 @@ def _safe_next(target) -> str:
app = create_app(SqlResultsRepo(cfg.RESULTS_DB_CONN),
users_store=SqlUserStore(cfg.RESULTS_DB_CONN))
users_store=SqlUserStore(cfg.RESULTS_DB_CONN),
**portal_settings(cfg.PORTAL_URLS, cfg.PORTAL_SERVER))