feat: add safe blocker resolution workflow
This commit is contained in:
+32
-13
@@ -8,9 +8,11 @@ from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import select
|
||||
|
||||
from .config import settings
|
||||
from .control_plane import dashboard_summary, list_blockers, list_installations, list_jobs, list_panel_events
|
||||
from .blocker_resolution import _company_key, resolve_blocker_with_context
|
||||
from .context_keys import ALLOWED_KEYS, ENUMS
|
||||
from .control_plane import blocker_detail, dashboard_summary, list_blockers, list_installations, list_jobs, list_panel_events
|
||||
from .human_auth import create_session, current_session, require_csrf, verify_password
|
||||
from .models import AdminUser, Installation, Agent, Job
|
||||
from .models import AdminUser, Agent, BlockedProduct, FiscalContextEntry, Installation, Job, Tenant
|
||||
from .jobs import create_job
|
||||
from .audit import record_audit
|
||||
|
||||
@@ -116,23 +118,40 @@ def router(session_factory):
|
||||
try: return render(request, "blockers.html", {"result": list_blockers(session, page=page, page_size=50, blocker_type=blocker_type), "auth": auth_data, "csrf_token": request.cookies.get("gti_csrf", "")})
|
||||
finally: session.close()
|
||||
|
||||
@routes.post("/blockers/{blocker_id}/resolve")
|
||||
async def resolve_blocker(request: Request, blocker_id: int):
|
||||
@routes.get("/blockers/{blocker_id}", response_class=HTMLResponse)
|
||||
def blocker_page(request: Request, blocker_id: int):
|
||||
session, auth_data, redirect = page_auth(request)
|
||||
if redirect: return redirect
|
||||
try:
|
||||
detail = blocker_detail(session, blocker_id)
|
||||
if detail is None: return HTMLResponse("Blocker not found", status_code=404)
|
||||
blocker = session.get(BlockedProduct, blocker_id)
|
||||
installation = session.get(Installation, blocker.installation_id) if blocker.installation_id else None
|
||||
tenant = session.get(Tenant, blocker.tenant_id)
|
||||
company_key = _company_key(blocker, installation, tenant)
|
||||
current_context = session.scalars(select(FiscalContextEntry).where(FiscalContextEntry.tenant_id == blocker.tenant_id, FiscalContextEntry.company_key == company_key)).all()
|
||||
value_choices = sorted({value for values in ENUMS.values() for value in values})
|
||||
return render(request, "blocker_detail.html", {"detail": detail, "auth": auth_data, "context_entries": current_context, "context_keys": sorted(ALLOWED_KEYS), "value_choices": value_choices, "error": None})
|
||||
finally: session.close()
|
||||
|
||||
@routes.post("/blockers/{blocker_id}/resolve-context")
|
||||
async def resolve_blocker_with_context_route(request: Request, blocker_id: int):
|
||||
session, auth_data, redirect = page_auth(request)
|
||||
if redirect: return redirect
|
||||
try:
|
||||
values = await form_values(request); web_session, admin = auth_data
|
||||
require_csrf(web_session, values.get("csrf_token"))
|
||||
if values.get("confirm") != "yes" or not values.get("context", "").strip(): return HTMLResponse("confirmation and context required", status_code=400)
|
||||
from .models import BlockedProduct
|
||||
blocker = session.get(BlockedProduct, blocker_id)
|
||||
if not blocker: return HTMLResponse("Blocker not found", status_code=404)
|
||||
from datetime import datetime, timezone
|
||||
blocker.resolved_at = datetime.now(timezone.utc); blocker.resolution_source = "HUMAN_PANEL"
|
||||
record_audit(session, admin_user_id=admin.id, tenant_id=blocker.tenant_id, action="RESOLVE_BLOCKER", target_type="blocker", target_id=str(blocker_id), context={"context": values["context"]})
|
||||
session.commit()
|
||||
payload = {key: values.get(key) for key in ("scope", "scope_key", "key", "value", "reason")}
|
||||
try:
|
||||
resolve_blocker_with_context(session, blocker_id, admin, payload, request_ip=request.client.host if request.client else None)
|
||||
except LookupError:
|
||||
return HTMLResponse("Blocker not found", status_code=404)
|
||||
except ValueError as error:
|
||||
detail = blocker_detail(session, blocker_id)
|
||||
return render(request, "blocker_detail.html", {"detail": detail, "auth": auth_data, "context_entries": [], "context_keys": sorted(ALLOWED_KEYS), "value_choices": sorted({value for values in ENUMS.values() for value in values}), "error": str(error)}, 422)
|
||||
return RedirectResponse("/blockers", status_code=303)
|
||||
finally: session.close()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
@routes.get("/jobs", response_class=HTMLResponse)
|
||||
def jobs(request: Request, page: int = 1, status: str | None = None):
|
||||
|
||||
Reference in New Issue
Block a user