You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
17 KiB
Python
402 lines
17 KiB
Python
from fastapi import APIRouter, Depends, Request
|
|
from fastapi.responses import HTMLResponse, RedirectResponse, Response
|
|
|
|
from admin_app.api.dependencies import get_optional_panel_staff_context
|
|
from admin_app.core import AdminSettings, AuthenticatedStaffContext, get_admin_settings
|
|
from admin_app.view.assets import PANEL_STATIC_MOUNT_NAME
|
|
from admin_app.view.rendering import render_login_page, render_panel_home, render_tool_review_page
|
|
from admin_app.view.view_models import (
|
|
AdminLoginPageView,
|
|
AdminPanelHomeView,
|
|
AdminPanelMetric,
|
|
AdminPanelModuleCard,
|
|
AdminPanelNavigationItem,
|
|
AdminPanelQuickAction,
|
|
AdminPanelRoadmapItem,
|
|
AdminPanelSurfaceLink,
|
|
AdminToolReviewPageView,
|
|
AdminToolReviewWorkflowStep,
|
|
)
|
|
from shared.contracts import AdminPermission, StaffRole
|
|
|
|
panel_router = APIRouter(tags=["panel"])
|
|
|
|
|
|
@panel_router.get("/panel", name="panel_entry")
|
|
def panel_entry(
|
|
request: Request,
|
|
current_context: AuthenticatedStaffContext | None = Depends(get_optional_panel_staff_context),
|
|
) -> RedirectResponse:
|
|
target_route_name = "panel_home" if current_context is not None else "admin_login_view"
|
|
return _redirect_to_route(request, target_route_name)
|
|
|
|
|
|
@panel_router.get("/panel/admin", response_class=HTMLResponse, name="panel_home")
|
|
def panel_home(
|
|
request: Request,
|
|
current_context: AuthenticatedStaffContext | None = Depends(get_optional_panel_staff_context),
|
|
) -> Response:
|
|
if current_context is None:
|
|
return _redirect_to_route(request, "admin_login_view")
|
|
|
|
settings = _resolve_settings(request)
|
|
view = _build_home_view(request, settings)
|
|
css_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="styles/panel.css"))
|
|
js_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="scripts/panel.js"))
|
|
return HTMLResponse(render_panel_home(view, css_href=css_href, js_href=js_href))
|
|
|
|
|
|
@panel_router.get("/login", response_class=HTMLResponse, name="admin_login_view")
|
|
def login_page(
|
|
request: Request,
|
|
current_context: AuthenticatedStaffContext | None = Depends(get_optional_panel_staff_context),
|
|
) -> Response:
|
|
if current_context is not None:
|
|
return _redirect_to_route(request, "panel_home")
|
|
|
|
settings = _resolve_settings(request)
|
|
view = _build_login_view(request, settings)
|
|
css_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="styles/panel.css"))
|
|
js_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="scripts/panel.js"))
|
|
return HTMLResponse(render_login_page(view, css_href=css_href, js_href=js_href))
|
|
|
|
|
|
@panel_router.get("/panel/tools/review", response_class=HTMLResponse, name="admin_tool_review_view")
|
|
def tool_review_page(
|
|
request: Request,
|
|
current_context: AuthenticatedStaffContext | None = Depends(get_optional_panel_staff_context),
|
|
) -> Response:
|
|
if current_context is None:
|
|
return _redirect_to_route(request, "admin_login_view")
|
|
|
|
settings = _resolve_settings(request)
|
|
view = _build_tool_review_view(request, settings)
|
|
css_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="styles/panel.css"))
|
|
js_href = str(request.url_for(PANEL_STATIC_MOUNT_NAME, path="scripts/panel.js"))
|
|
return HTMLResponse(render_tool_review_page(view, css_href=css_href, js_href=js_href))
|
|
|
|
|
|
def _build_home_view(request: Request, settings: AdminSettings) -> AdminPanelHomeView:
|
|
panel_href = str(request.url_for("panel_home"))
|
|
tool_review_view_href = str(request.url_for("admin_tool_review_view"))
|
|
system_configuration_href = _build_prefixed_path(settings.admin_api_prefix, "/system/configuration")
|
|
audit_href = _build_prefixed_path(settings.admin_api_prefix, "/audit/events")
|
|
|
|
return AdminPanelHomeView(
|
|
service="orquestrador-admin",
|
|
app_name=settings.admin_app_name,
|
|
panel_title="Painel Administrativo",
|
|
panel_subtitle=(
|
|
"Area interna protegida para operar o admin com mais clareza, foco e navegacao orientada por fluxo."
|
|
),
|
|
environment=settings.admin_environment,
|
|
version=settings.admin_version,
|
|
api_prefix=settings.admin_api_prefix or "/",
|
|
release_label="Bootstrap UI v1",
|
|
navigation=(
|
|
AdminPanelNavigationItem(
|
|
label="Dashboard",
|
|
href=panel_href,
|
|
description="Entrada principal do ambiente interno.",
|
|
badge="Ativo",
|
|
is_active=True,
|
|
),
|
|
AdminPanelNavigationItem(
|
|
label="Revisao de tools",
|
|
href=tool_review_view_href,
|
|
description="Fluxo humano de revisao, aprovacao e ativacao.",
|
|
badge="Operacao",
|
|
),
|
|
AdminPanelNavigationItem(
|
|
label="Areas do sistema",
|
|
href="#modules",
|
|
description="Mapa claro dos modulos internos disponiveis.",
|
|
badge="Painel",
|
|
),
|
|
AdminPanelNavigationItem(
|
|
label="Fluxo recomendado",
|
|
href="#workflow",
|
|
description="Sequencia sugerida para operar o admin.",
|
|
badge="Guia",
|
|
),
|
|
),
|
|
quick_actions=(
|
|
AdminPanelQuickAction(
|
|
label="Revisar tools",
|
|
href=tool_review_view_href,
|
|
button_class="btn-dark",
|
|
),
|
|
AdminPanelQuickAction(
|
|
label="Ver areas",
|
|
href="#modules",
|
|
button_class="btn-outline-dark",
|
|
),
|
|
AdminPanelQuickAction(
|
|
label="Ver fluxo",
|
|
href="#workflow",
|
|
button_class="btn-outline-secondary",
|
|
),
|
|
),
|
|
metrics=(
|
|
AdminPanelMetric(
|
|
label="Runtimes independentes",
|
|
value="2",
|
|
description="Produto e admin seguem isolados para deploy e operacao.",
|
|
),
|
|
AdminPanelMetric(
|
|
label="Perfis internos",
|
|
value=str(len(StaffRole)),
|
|
description="Hierarquia base com viewer, staff e admin.",
|
|
),
|
|
AdminPanelMetric(
|
|
label="Permissoes administrativas",
|
|
value=str(len(AdminPermission)),
|
|
description="Camada pronta para crescer por modulo sem misturar contexto.",
|
|
),
|
|
AdminPanelMetric(
|
|
label="Refresh token",
|
|
value=f"{settings.admin_auth_refresh_token_ttl_days} dias",
|
|
description="Sessao web persistida com renovacao controlada.",
|
|
),
|
|
),
|
|
modules=(
|
|
AdminPanelModuleCard(
|
|
eyebrow="Fluxo principal",
|
|
title="Revisao de tools",
|
|
description="A principal area operacional do painel para leitura da fila, aprovacao humana e ativacao controlada.",
|
|
status_label="Tela ativa",
|
|
status_variant="success",
|
|
highlights=(
|
|
"Fila protegida por sessao web",
|
|
"Catalogo ativo para comparacao",
|
|
"Leitura clara do workflow de aprovacao",
|
|
),
|
|
cta_label="Abrir revisao",
|
|
href=tool_review_view_href,
|
|
is_available=True,
|
|
),
|
|
AdminPanelModuleCard(
|
|
eyebrow="Acompanhamento",
|
|
title="Configuracao do sistema",
|
|
description="Snapshot do runtime administrativo, politicas de seguranca e dados de sessao do painel.",
|
|
status_label="API pronta",
|
|
status_variant="secondary",
|
|
highlights=(
|
|
"Runtime e banco monitorados",
|
|
"Politicas de credencial centralizadas",
|
|
"Base pronta para futura tela dedicada",
|
|
),
|
|
),
|
|
AdminPanelModuleCard(
|
|
eyebrow="Governanca",
|
|
title="Auditoria operacional",
|
|
description="Eventos de login, logout, aprovacao e publicacao continuam registrados para rastreabilidade.",
|
|
status_label="Auditavel",
|
|
status_variant="secondary",
|
|
highlights=(
|
|
"Historico de operacao interna",
|
|
"Base para filtros e timeline",
|
|
"Suporte a conformidade do fluxo administrativo",
|
|
),
|
|
),
|
|
AdminPanelModuleCard(
|
|
eyebrow="Seguranca",
|
|
title="Sessao administrativa",
|
|
description="Acesso ao painel protegido por StaffAccount, token assinado e refresh token rotacionado.",
|
|
status_label="Protegido",
|
|
status_variant="success",
|
|
highlights=(
|
|
"StaffAccount isolado do usuario final",
|
|
"Cookies httpOnly no navegador",
|
|
"Rotacao controlada da sessao web",
|
|
),
|
|
),
|
|
),
|
|
surface_links=(
|
|
AdminPanelSurfaceLink(
|
|
method="Acesso",
|
|
label="Dashboard administrativa",
|
|
href=panel_href,
|
|
description="Entrada principal do time interno depois do login.",
|
|
),
|
|
AdminPanelSurfaceLink(
|
|
method="Operacao",
|
|
label="Revisao de tools",
|
|
href=tool_review_view_href,
|
|
description="Area com fila, contrato e catalogo ativo para tomada de decisao.",
|
|
),
|
|
AdminPanelSurfaceLink(
|
|
method="Runtime",
|
|
label="Configuracao do sistema",
|
|
href=system_configuration_href,
|
|
description="Snapshot tecnico do ambiente, mantido como superficie protegida enquanto a tela visual nao chega.",
|
|
),
|
|
AdminPanelSurfaceLink(
|
|
method="Auditoria",
|
|
label="Eventos administrativos",
|
|
href=audit_href,
|
|
description="Consulta de eventos internos para rastrear operacoes sensiveis.",
|
|
),
|
|
),
|
|
roadmap=(
|
|
AdminPanelRoadmapItem(
|
|
step="01",
|
|
title="Entrar pelo login administrativo",
|
|
description="A sessao web libera o ambiente interno e evita navegacao confusa antes da autenticacao.",
|
|
status_label="Obrigatorio",
|
|
),
|
|
AdminPanelRoadmapItem(
|
|
step="02",
|
|
title="Passar pela dashboard",
|
|
description="A home protegida organiza os modulos e mostra por onde comecar a operacao.",
|
|
status_label="Entrada",
|
|
),
|
|
AdminPanelRoadmapItem(
|
|
step="03",
|
|
title="Abrir revisao de tools",
|
|
description="Use o hub de revisao para analisar fila, contrato e ativacao das tools.",
|
|
status_label="Principal",
|
|
),
|
|
AdminPanelRoadmapItem(
|
|
step="04",
|
|
title="Consultar runtime e auditoria",
|
|
description="Quando necessario, acompanhe configuracao e eventos do admin para suportar a decisao operacional.",
|
|
status_label="Suporte",
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def _build_login_view(request: Request, settings: AdminSettings) -> AdminLoginPageView:
|
|
dashboard_href = str(request.url_for("panel_home"))
|
|
auth_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/auth/login")
|
|
session_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/auth/session")
|
|
logout_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/auth/logout")
|
|
password_requirements = []
|
|
if settings.admin_auth_password_require_uppercase:
|
|
password_requirements.append("maiuscula")
|
|
if settings.admin_auth_password_require_lowercase:
|
|
password_requirements.append("minuscula")
|
|
if settings.admin_auth_password_require_digit:
|
|
password_requirements.append("digito")
|
|
if settings.admin_auth_password_require_symbol:
|
|
password_requirements.append("simbolo")
|
|
|
|
password_policy_label = (
|
|
f"Minimo de {settings.admin_auth_password_min_length} caracteres"
|
|
+ (f" com {', '.join(password_requirements)}." if password_requirements else ".")
|
|
)
|
|
|
|
return AdminLoginPageView(
|
|
app_name=settings.admin_app_name,
|
|
title="Login administrativo",
|
|
subtitle=(
|
|
"Entre primeiro com sua conta interna. A dashboard e os modulos do sistema so aparecem depois da autenticacao."
|
|
),
|
|
environment=settings.admin_environment,
|
|
version=settings.admin_version,
|
|
dashboard_href=dashboard_href,
|
|
auth_endpoint=auth_endpoint,
|
|
session_endpoint=session_endpoint,
|
|
logout_endpoint=logout_endpoint,
|
|
email_placeholder="voce@empresa.com",
|
|
password_placeholder="Sua senha administrativa",
|
|
access_token_ttl_label=f"{settings.admin_auth_access_token_ttl_minutes} minutos",
|
|
refresh_token_ttl_label=f"{settings.admin_auth_refresh_token_ttl_days} dias",
|
|
password_policy_label=password_policy_label,
|
|
security_highlights=(
|
|
"Identidade separada do usuario de atendimento",
|
|
"Rotacao de refresh token ja implementada",
|
|
"Trilha de auditoria para login e logout",
|
|
),
|
|
integration_notes=(
|
|
"A dashboard administrativa so aparece depois da autenticacao do StaffAccount.",
|
|
"Revisao, configuracao e operacao interna ficam atras da sessao web do painel.",
|
|
"Cookies httpOnly e refresh token rotacionado mantem a sessao do navegador protegida.",
|
|
),
|
|
)
|
|
|
|
|
|
def _build_tool_review_view(request: Request, settings: AdminSettings) -> AdminToolReviewPageView:
|
|
dashboard_href = str(request.url_for("panel_home"))
|
|
login_href = str(request.url_for("admin_login_view"))
|
|
overview_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/tools/overview")
|
|
contracts_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/tools/contracts")
|
|
review_queue_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/tools/review-queue")
|
|
publications_endpoint = _build_prefixed_path(settings.admin_api_prefix, "/panel/tools/publications")
|
|
|
|
return AdminToolReviewPageView(
|
|
app_name=settings.admin_app_name,
|
|
title="Revisao, aprovacao e ativacao",
|
|
subtitle=(
|
|
"Hub visual para o time interno acompanhar a fila de revisao, validar o contrato compartilhado e inspecionar o catalogo de tools ativas antes da ativacao."
|
|
),
|
|
environment=settings.admin_environment,
|
|
version=settings.admin_version,
|
|
dashboard_href=dashboard_href,
|
|
login_href=login_href,
|
|
overview_endpoint=overview_endpoint,
|
|
contracts_endpoint=contracts_endpoint,
|
|
review_queue_endpoint=review_queue_endpoint,
|
|
publications_endpoint=publications_endpoint,
|
|
workflow=(
|
|
AdminToolReviewWorkflowStep(
|
|
eyebrow="Leitura inicial",
|
|
title="Revisar fila",
|
|
description="Carregar a fila de geracao e entender em que gate cada item se encontra.",
|
|
status_label="Revisao",
|
|
status_variant="info",
|
|
),
|
|
AdminToolReviewWorkflowStep(
|
|
eyebrow="Decisao humana",
|
|
title="Aprovar com criterio",
|
|
description="Conferir contrato, parametros e prontidao tecnica antes de liberar a proxima etapa.",
|
|
status_label="Aprovacao",
|
|
status_variant="warning",
|
|
),
|
|
AdminToolReviewWorkflowStep(
|
|
eyebrow="Publicacao",
|
|
title="Ativar no catalogo",
|
|
description="Usar o catalogo publicado como referencia para a versao que chega ao runtime de produto.",
|
|
status_label="Ativacao",
|
|
status_variant="success",
|
|
),
|
|
),
|
|
review_notes=(
|
|
"Conferir se o gate do item combina com o estado esperado do lifecycle.",
|
|
"Observar se a descricao e o objetivo operacional da tool estao claros para o time.",
|
|
"Usar o catalogo ativo como comparativo antes de promover uma nova versao.",
|
|
),
|
|
approval_notes=(
|
|
"Verificar nome, descricao e semantica dos parametros antes da aprovacao.",
|
|
"Confirmar se a tool respeita a separacao entre admin e product definida nas ADRs.",
|
|
"Checar se a publicacao planejada e auditavel e segura para o runtime de produto.",
|
|
),
|
|
activation_notes=(
|
|
"Publicacoes ativas exigem papel com permissao publish_tools.",
|
|
"A leitura do catalogo e feita via sessao web do painel para facilitar a operacao do navegador.",
|
|
"Sem permissao de publicacao, a tela continua util para revisao, mas bloqueia o catalogo ativo.",
|
|
),
|
|
)
|
|
|
|
|
|
def _redirect_to_route(request: Request, route_name: str) -> RedirectResponse:
|
|
return RedirectResponse(url=str(request.url_for(route_name)), status_code=302)
|
|
|
|
|
|
def _resolve_settings(request: Request) -> AdminSettings:
|
|
app_settings = getattr(request.app.state, "admin_settings", None)
|
|
if isinstance(app_settings, AdminSettings):
|
|
return app_settings
|
|
return get_admin_settings()
|
|
|
|
|
|
def _build_prefixed_path(api_prefix: str, path: str) -> str:
|
|
normalized_prefix = api_prefix.rstrip("/")
|
|
normalized_path = path if path.startswith("/") else f"/{path}"
|
|
if not normalized_prefix:
|
|
return normalized_path
|
|
if normalized_path == "/":
|
|
return f"{normalized_prefix}/"
|
|
return f"{normalized_prefix}{normalized_path}"
|