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.services import ToolManagementService from admin_app.view.assets import PANEL_STATIC_MOUNT_NAME from admin_app.view.rendering import ( render_collaborator_management_page, render_login_page, render_panel_home, render_tool_intake_page, render_tool_review_page, ) from admin_app.view.view_models import ( AdminCollaboratorManagementPageView, AdminLoginPageView, AdminPanelHomeView, AdminPanelMetric, AdminPanelModuleCard, AdminPanelNavigationItem, AdminPanelQuickAction, AdminPanelRoadmapItem, AdminPanelSurfaceLink, AdminToolIntakeDomainOption, AdminToolIntakePageView, AdminToolIntakeParameterTypeOption, AdminToolReviewPageView, AdminToolReviewWorkflowStep, ) from shared.contracts import AdminPermission, StaffRole, role_has_permission 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, current_context) 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/new", response_class=HTMLResponse, name="admin_tool_intake_view") def tool_intake_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_intake_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_intake_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)) @panel_router.get("/panel/colaboradores/gestao", response_class=HTMLResponse, name="admin_collaborator_management_view") def collaborator_management_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") if not role_has_permission(current_context.principal.role, AdminPermission.MANAGE_STAFF_ACCOUNTS): return _redirect_to_route(request, "panel_home") settings = _resolve_settings(request) view = _build_collaborator_management_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_collaborator_management_page(view, css_href=css_href, js_href=js_href)) def _build_home_view( request: Request, settings: AdminSettings, current_context: AuthenticatedStaffContext, ) -> AdminPanelHomeView: panel_href = str(request.url_for("panel_home")) tool_intake_view_href = str(request.url_for("admin_tool_intake_view")) tool_review_view_href = str(request.url_for("admin_tool_review_view")) collaborator_management_view_href = str(request.url_for("admin_collaborator_management_view")) system_configuration_href = _build_prefixed_path(settings.admin_api_prefix, "/system/configuration") audit_href = _build_prefixed_path(settings.admin_api_prefix, "/audit/events") can_manage_collaborators = role_has_permission( current_context.principal.role, AdminPermission.MANAGE_STAFF_ACCOUNTS, ) navigation = [ AdminPanelNavigationItem( label="Dashboard", href=panel_href, description="Entrada principal do ambiente interno.", badge="Ativo", is_active=True, ), AdminPanelNavigationItem( label="Cadastro de tools", href=tool_intake_view_href, description="Pre-cadastro validado para novas tools antes da revisao.", badge="Novo", ), 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="Cadastrar tool", href=tool_intake_view_href, button_class="btn-dark", ), AdminPanelQuickAction( label="Revisar tools", href=tool_review_view_href, button_class="btn-outline-dark", ), AdminPanelQuickAction( label="Ver areas", href="#modules", button_class="btn-outline-secondary", ), ] modules = [ AdminPanelModuleCard( eyebrow="Fluxo de entrada", title="Cadastro de tools", description="Tela real para o colaborador preencher metadados, definir parametros e validar o pre-cadastro antes da revisao humana.", status_label="Tela ativa", status_variant="success", highlights=( "Formulario protegido por sessao web", "Preview validado antes da persistencia", "Direcao clara para revisao de diretor", ), cta_label="Abrir cadastro", href=tool_intake_view_href, is_available=True, ), AdminPanelModuleCard( eyebrow="Fluxo principal", title="Revisao de tools", description="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", ), ), ] surface_links = [ AdminPanelSurfaceLink( method="Acesso", label="Dashboard administrativa", href=panel_href, description="Entrada principal do time interno depois do login.", ), AdminPanelSurfaceLink( method="Cadastro", label="Nova tool", href=tool_intake_view_href, description="Formulario real para validar o pre-cadastro de uma nova tool.", ), 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="Cadastrar ou validar o pre-draft", description="Use a nova tela para descrever a tool, seus parametros e o objetivo operacional antes da revisao.", status_label="Cadastro", ), AdminPanelRoadmapItem( step="04", title="Abrir revisao de tools", description="Encaminhe a ferramenta para analise humana, aprovacao e ativacao controlada.", status_label="Principal", ), AdminPanelRoadmapItem( step="05", title="Consultar runtime e auditoria", description="Quando necessario, acompanhe configuracao e eventos do admin para suportar a decisao operacional.", status_label="Suporte", ), ] if can_manage_collaborators: navigation.insert( 3, AdminPanelNavigationItem( label="Colaboradores", href=collaborator_management_view_href, description="Cadastro e governanca de acessos internos, exclusivo para diretor.", badge="Diretor", ), ) quick_actions.insert( 2, AdminPanelQuickAction( label="Gerir equipe", href=collaborator_management_view_href, button_class="btn-outline-dark", ), ) modules.insert( 2, AdminPanelModuleCard( eyebrow="Governanca de acesso", title="Gestao de colaboradores", description="Tela dedicada para o diretor criar contas internas, acompanhar o status da equipe e manter a entrada administrativa sob controle.", status_label="Tela ativa", status_variant="dark", highlights=( "Criacao de colaborador com senha inicial", "Ativacao e desativacao sem tocar no banco manualmente", "Fluxo exclusivo para diretor", ), cta_label="Abrir equipe", href=collaborator_management_view_href, is_available=True, ), ) surface_links.insert( 3, AdminPanelSurfaceLink( method="Equipe", label="Gestao de colaboradores", href=collaborator_management_view_href, description="Controle de acesso interno para cadastrar e administrar a equipe administrativa.", ), ) 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="Cadastrar ou validar o pre-draft", description="Use a nova tela para descrever a tool, seus parametros e o objetivo operacional antes da revisao.", status_label="Cadastro", ), AdminPanelRoadmapItem( step="04", title="Organizar a equipe interna", description="Diretores podem cadastrar novos colaboradores e controlar rapidamente o status de acesso administrativo.", status_label="Diretor", ), AdminPanelRoadmapItem( step="05", title="Abrir revisao de tools", description="Encaminhe a ferramenta para analise humana, aprovacao e ativacao controlada.", status_label="Principal", ), AdminPanelRoadmapItem( step="06", title="Consultar runtime e auditoria", description="Quando necessario, acompanhe configuracao e eventos do admin para suportar a decisao operacional.", status_label="Suporte", ), ] 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=tuple(navigation), quick_actions=tuple(quick_actions), 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 colaborador e diretor.", ), 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=tuple(modules), surface_links=tuple(surface_links), roadmap=tuple(roadmap), ) 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") 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, 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_intake_view(request: Request, settings: AdminSettings) -> AdminToolIntakePageView: service = ToolManagementService(settings) form_payload = service.build_draft_form_payload() return AdminToolIntakePageView( app_name=settings.admin_app_name, title="Cadastro de nova tool", subtitle=( "Formulario guiado para o colaborador estruturar uma nova tool, validar o pre-draft e encaminhar a proposta para revisao de diretor." ), environment=settings.admin_environment, version=settings.admin_version, dashboard_href=str(request.url_for("panel_home")), review_href=str(request.url_for("admin_tool_review_view")), intake_endpoint=_build_prefixed_path(settings.admin_api_prefix, "/panel/tools/drafts/intake"), domain_options=tuple( AdminToolIntakeDomainOption( value=item["value"], label=item["label"], description=item["description"], ) for item in form_payload["domain_options"] ), parameter_type_options=tuple( AdminToolIntakeParameterTypeOption( value=item["code"].value, label=item["label"], description=item["description"], ) for item in form_payload["parameter_types"] ), naming_rules=tuple(form_payload["naming_rules"]), submission_notes=tuple(form_payload["submission_notes"]), approval_notes=tuple(form_payload["approval_notes"]), ) def _build_tool_review_view(request: Request, settings: AdminSettings) -> AdminToolReviewPageView: dashboard_href = str(request.url_for("panel_home")) 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, 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 _build_collaborator_management_view( request: Request, settings: AdminSettings, ) -> AdminCollaboratorManagementPageView: 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 AdminCollaboratorManagementPageView( app_name=settings.admin_app_name, title="Gestao de colaboradores", subtitle=( "Tela exclusiva de diretor para organizar a equipe interna, criar novos acessos administrativos e controlar rapidamente quem segue ativo no painel." ), environment=settings.admin_environment, version=settings.admin_version, dashboard_href=str(request.url_for("panel_home")), collection_endpoint=_build_prefixed_path(settings.admin_api_prefix, "/panel/colaboradores"), password_policy_label=password_policy_label, onboarding_notes=( "Novos acessos nascem sempre com papel de colaborador.", "A senha inicial ja respeita a mesma politica do login administrativo.", "Ativar ou desativar colaborador nao exige acesso direto ao banco.", ), governance_notes=( "Somente diretor acessa esta tela e as rotas de gestao de colaboradores.", "Cada criacao e alteracao de status gera trilha de auditoria administrativa.", "A conta de diretor continua fora deste fluxo para evitar mudancas acidentais na governanca principal.", ), ) 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}"