Files
GTI_GERENCIADOR_FISCAL/central/app/web.py
T

141 lines
8.0 KiB
Python

"""Server-rendered operator pages for the GTI Fiscal control panel."""
from urllib.parse import parse_qs
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, RedirectResponse
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 .human_auth import create_session, current_session, require_csrf, verify_password
from .models import AdminUser, Installation, Agent
from .jobs import create_job
from .audit import record_audit
def router(session_factory):
routes = APIRouter()
templates = Jinja2Templates(directory="central/templates")
def auth(request, session):
return current_session(request, session)
def render(request, template, context=None, status_code=200):
return templates.TemplateResponse(template, {"request": request, **(context or {})}, status_code=status_code)
@routes.get("/login", response_class=HTMLResponse)
def login_page(request: Request):
return render(request, "login.html", {"error": None})
@routes.post("/login")
async def login(request: Request):
values = {key: items[0] for key, items in parse_qs((await request.body()).decode()).items() if items}
with session_factory() as session:
admin = session.scalar(select(AdminUser).where(AdminUser.username == values.get("username", "").strip().lower()))
if not admin or not admin.is_active or not verify_password(values.get("password", ""), admin.password_hash):
return render(request, "login.html", {"error": "Credenciais inválidas"}, 401)
token, csrf = create_session(session, admin, request)
response = RedirectResponse("/", status_code=303)
response.set_cookie(settings.web_cookie_name, token, max_age=settings.web_session_seconds, httponly=True, secure=settings.web_cookie_secure, samesite="lax")
response.set_cookie("gti_csrf", csrf, max_age=settings.web_session_seconds, httponly=False, secure=settings.web_cookie_secure, samesite="lax")
return response
@routes.post("/logout")
def logout(request: Request):
with session_factory() as session:
web_session, admin = current_session(request, session)
if web_session:
session.delete(web_session); session.commit()
response = RedirectResponse("/login", status_code=303)
response.delete_cookie(settings.web_cookie_name); response.delete_cookie("gti_csrf")
return response
def page_auth(request):
session = session_factory()
web_session, admin = current_session(request, session)
if not admin:
session.close(); return None, None, RedirectResponse("/login", status_code=303)
return session, (web_session, admin), None
async def form_values(request):
return {key: items[0] for key, items in parse_qs((await request.body()).decode()).items() if items}
@routes.get("/", response_class=HTMLResponse)
def dashboard(request: Request):
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
try: return render(request, "dashboard.html", {"summary": dashboard_summary(session), "auth": auth_data})
finally: session.close()
@routes.get("/agents", response_class=HTMLResponse)
def agents(request: Request, page: int = 1, search: str | None = None, health: str | None = None):
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
try: return render(request, "agents.html", {"result": list_installations(session, page=page, page_size=50, search=search, health=health), "auth": auth_data, "search": search or "", "health_filter": health or ""})
finally: session.close()
@routes.get("/agents/{agent_id}", response_class=HTMLResponse)
def agent_detail(request: Request, agent_id: int, tab: str = "overview"):
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
try:
result = list_installations(session, page=1, page_size=100)
detail = next((item for item in result["items"] if item["agent_id"] == agent_id), None)
if detail is None: return HTMLResponse("Agent not found", status_code=404)
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
return render(request, "agent_detail.html", {"detail": detail, "tab": tab, "blockers": list_blockers(session, tenant_id=installation.tenant_id, page=1, page_size=25), "jobs": list_jobs(session, agent_id=agent_id, page=1, page_size=25), "events": list_panel_events(session, installation.id, page=1, page_size=25), "auth": auth_data, "csrf_token": request.cookies.get("gti_csrf", "")})
finally: session.close()
@routes.post("/agents/{agent_id}/reprocess")
async def reprocess_agent(request: Request, agent_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": return HTMLResponse("confirmation required", status_code=400)
agent = session.get(Agent, agent_id)
installation = session.scalar(select(Installation).where(Installation.agent_id == agent_id))
if not agent or not installation: return HTMLResponse("Agent not found", status_code=404)
job = create_job(session, agent, "REPROCESS_ALL", {})
record_audit(session, admin_user_id=admin.id, tenant_id=installation.tenant_id, action="REPROCESS_ALL", target_type="installation", target_id=installation.installation_id, context={"job_id": job.id})
session.commit()
return RedirectResponse(f"/agents/{agent_id}?tab=jobs", status_code=303)
finally: session.close()
@routes.get("/blockers", response_class=HTMLResponse)
def blockers(request: Request, page: int = 1, blocker_type: str | None = None):
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
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):
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()
return RedirectResponse("/blockers", status_code=303)
finally: session.close()
@routes.get("/jobs", response_class=HTMLResponse)
def jobs(request: Request, page: int = 1, status: str | None = None):
session, auth_data, redirect = page_auth(request)
if redirect: return redirect
try: return render(request, "jobs.html", {"result": list_jobs(session, page=page, page_size=50, status=status), "auth": auth_data})
finally: session.close()
return routes