Portal Delete run (Super Admin), lab codes #3/#4, Z23 pointer #45, T1017 #46, payer note, login security-image wait
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a8af44e673
commit
fafa5285eb
21 files changed
+1509
-63
No files matched your search
+45
-5
@@ -56,7 +56,7 @@ from ecw_bot.core.run_events import run_id_of # noqa: E402
|
||||
from .repo import SqlResultsRepo # noqa: E402
|
||||
from . import run_status # noqa: E402
|
||||
from . import runs as run_view # noqa: E402
|
||||
from .users import (Accounts, CAN_ADMIN, CAN_RUN, # noqa: E402
|
||||
from .users import (Accounts, CAN_ADMIN, CAN_DELETE, CAN_RUN, # noqa: E402
|
||||
EcareSignIn, IDLE_MINUTES, LOCK_AFTER, LOCK_MINUTES,
|
||||
MANAGES, Sessions, SqlUserStore, UserError, is_ecare)
|
||||
from .bot_runner import (AlreadyRunning, BotRunner, # noqa: E402
|
||||
@@ -109,6 +109,13 @@ _DB_PROBLEMS = {
|
||||
"42000": ("No permission to read the results table",
|
||||
"The Windows login needs db_datareader on Charge_Validation."),
|
||||
}
|
||||
# The Portal's SQL login reads; deleting a run needs one more grant.
|
||||
_DELETE_PROBLEMS = {
|
||||
"42000": ("No permission to delete runs",
|
||||
"The Portal's SQL login needs GRANT DELETE ON "
|
||||
"dbo.CV_ChargeSummary — see the end of "
|
||||
"tools/sql/create_tables.sql."),
|
||||
}
|
||||
_DB_DEFAULT = (DB_DOWN, "Check that SQL Server is running and that "
|
||||
"RESULTS_DB_CONN in .env points at the "
|
||||
"Charge_Validation database.")
|
||||
@@ -211,7 +218,9 @@ def _filters(run_at, category, claim_no):
|
||||
return when, category, claim
|
||||
|
||||
|
||||
def _db(fn, *args, **kwargs):
|
||||
def _db(fn, *args, problems=None, **kwargs):
|
||||
"""`fn(*args, **kwargs)`; a database error becomes DbUnavailable, named
|
||||
by its SQLSTATE (`problems` adds or overrides names for one call)."""
|
||||
try:
|
||||
return fn(*args, **kwargs)
|
||||
except UserError:
|
||||
@@ -219,7 +228,8 @@ def _db(fn, *args, **kwargs):
|
||||
except Exception as exc:
|
||||
state = exc.args[0] if exc.args and isinstance(exc.args[0], str) \
|
||||
else ""
|
||||
title, hint = _DB_PROBLEMS.get(state, _DB_DEFAULT)
|
||||
title, hint = {**_DB_PROBLEMS, **(problems or {})}.get(state,
|
||||
_DB_DEFAULT)
|
||||
print(f"[WEB] {title}: {results_db.safe_error(exc)}")
|
||||
raise DbUnavailable(title, hint) from exc
|
||||
|
||||
@@ -429,6 +439,7 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
|
||||
"can_run": bool(can_start and user
|
||||
and user["role"] in CAN_RUN),
|
||||
"is_admin": bool(user and user["role"] in CAN_ADMIN),
|
||||
"can_delete": bool(user and user["role"] in CAN_DELETE),
|
||||
**context})
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
@@ -464,11 +475,17 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
|
||||
|
||||
@app.get("/day/{day}", response_class=HTMLResponse)
|
||||
def day_page(request: Request, day: str, category: str = None,
|
||||
claim_no: str = None, claim: str = None):
|
||||
claim_no: str = None, claim: str = None, msg: str = ""):
|
||||
when = _day(day)
|
||||
_, cat, search = _filters(None, category, claim_no)
|
||||
_, _, opened = _filters(None, None, claim)
|
||||
rows = _db(repo.day_results, when)
|
||||
runs = {}
|
||||
for r in rows:
|
||||
run = runs.setdefault(r["run_at"], {
|
||||
"run_at": r["run_at"], "run_mode": r.get("run_mode"),
|
||||
"started_by": r.get("started_by"), "claims": 0})
|
||||
run["claims"] += 1
|
||||
shown = [_for_panel(r) for r in rows
|
||||
if (not cat or r.get("category") == cat)
|
||||
and (not search or search in str(r.get("claim_no") or ""))]
|
||||
@@ -477,7 +494,30 @@ def create_app(repo, allowed_hosts=ALLOWED_HOSTS, runner=None,
|
||||
[_long_date(when), None]],
|
||||
day=when, counts=_counts(rows), rows=shown,
|
||||
category=cat or "", claim_no=search or "",
|
||||
opened=opened or "")
|
||||
opened=opened or "", runs=list(runs.values()),
|
||||
message=msg[:200])
|
||||
|
||||
@app.post("/admin/runs/delete", response_class=HTMLResponse)
|
||||
def run_delete(request: Request, run_at: str = Form(""),
|
||||
token: str = Form("")):
|
||||
"""One run's rows out of the results table — the Super Admin's
|
||||
only (operator, 2026-10-28). Our own data, never eCW's; the run's
|
||||
Logs files stay. Recorded in the Sign-in history."""
|
||||
_check_token(request, token)
|
||||
me = _need(request, CAN_DELETE)["username"]
|
||||
try:
|
||||
when = datetime.fromisoformat(run_at)
|
||||
except ValueError:
|
||||
raise BadFilter(f"Not a run time: {run_at}")
|
||||
gone = _db(repo.delete_run, when, problems=_DELETE_PROBLEMS)
|
||||
if gone:
|
||||
_db(store.record, f"run {when:%m/%d/%Y} {_clock(when)}",
|
||||
"run_deleted", by=me)
|
||||
claims = f"{gone} claim{'' if gone == 1 else 's'}"
|
||||
return RedirectResponse(
|
||||
f"/day/{when.date().isoformat()}?" + urlencode(
|
||||
{"msg": f"The {_clock(when)} run is deleted: {claims} "
|
||||
f"deleted"}), status_code=303)
|
||||
|
||||
@app.get("/claim", response_class=HTMLResponse)
|
||||
def claim_page(request: Request, claim_no: str = None):
|
||||
|
||||
Reference in new issue
Block a user