feat: GTI Gerenciador as the single source of truth for clients; bump to 1.1.2
Tenant gains source (LOCAL|GTI_GERENCIADOR), external_client_id, external_code, active and synced_at (migration 0024). central/app/client_sync.py fetches from the panel's new GET /internal/fiscal/clients (dedicated bearer token, never MySQL credentials) and upserts Tenant rows, never touching a LOCAL row. /agents/new now offers only active GTI_GERENCIADOR tenants, with a client-side search box; the POST validates the same rule server-side, not just in the dropdown. central/scripts/sync_clients_from_gerenciador.py is the operator-run sync + reconciliation report. GTI_FISCAL_VERSION/AGENT_VERSION bumped to 1.1.2 (release/VERSION single source of truth), incorporating the installation_id persistence fix. Rebuilt the EXE installer trio at 1.1.2 (scripts/build_windows_installer.ps1). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+19
-7
@@ -10,7 +10,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import select
|
||||
|
||||
from . import agent_distribution, config, enrollment, sso
|
||||
from . import agent_distribution, client_sync, config, enrollment, sso
|
||||
from .config import settings
|
||||
from .blocker_resolution import BlockerResolutionUnavailable, _company_key, resolve_blocker_with_context
|
||||
from .context_keys import ALLOWED_KEYS, ENUMS
|
||||
@@ -200,14 +200,22 @@ def router(session_factory):
|
||||
return RedirectResponse("/agents/releases", status_code=303)
|
||||
finally: session.close()
|
||||
|
||||
def _active_gerenciador_tenants(session, search: str | None = None):
|
||||
# GTI Gerenciador is the single source of truth for clients (central/app/
|
||||
# client_sync.py) — a new installation never offers a LOCAL (dev/test) or
|
||||
# inactive tenant, so it can never pick a client that doesn't exist there.
|
||||
query = select(Tenant).where(Tenant.source == client_sync.SOURCE, Tenant.active.is_(True))
|
||||
if search: query = query.where(Tenant.name.ilike(f"%{search}%"))
|
||||
return session.scalars(query.order_by(Tenant.name)).all()
|
||||
|
||||
@routes.get("/agents/new", response_class=HTMLResponse)
|
||||
def new_installation_form(request: Request):
|
||||
def new_installation_form(request: Request, search: str | None = None):
|
||||
session, auth_data, redirect = page_auth(request)
|
||||
if redirect: return redirect
|
||||
try:
|
||||
tenants = session.scalars(select(Tenant).order_by(Tenant.name)).all()
|
||||
tenants = _active_gerenciador_tenants(session, search)
|
||||
releases = session.scalars(select(AgentRelease).where(AgentRelease.status == "PUBLISHED").order_by(AgentRelease.created_at.desc())).all()
|
||||
return render(request, "agent_new_installation.html", {"tenants": tenants, "releases": releases, "auth": auth_data, "error": None})
|
||||
return render(request, "agent_new_installation.html", {"tenants": tenants, "releases": releases, "auth": auth_data, "error": None, "search": search or ""})
|
||||
finally: session.close()
|
||||
|
||||
@routes.post("/agents/new")
|
||||
@@ -224,13 +232,17 @@ def router(session_factory):
|
||||
errors = []
|
||||
tenant = session.get(Tenant, int(tenant_id)) if tenant_id and tenant_id.isdigit() else None
|
||||
release = session.get(AgentRelease, int(release_id)) if release_id and release_id.isdigit() else None
|
||||
if not tenant: errors.append("Selecione um cliente válido.")
|
||||
# Server-side, not just a hidden option in the form: a LOCAL or
|
||||
# inactive tenant can never receive a new installation, matching
|
||||
# GTI Gerenciador's own active/inactive rule.
|
||||
if tenant and (tenant.source != client_sync.SOURCE or not tenant.active): tenant = None
|
||||
if not tenant: errors.append("Selecione um cliente ativo válido (cadastrado no GTI Gerenciador).")
|
||||
if not display_name: errors.append("Informe um nome para a instalação.")
|
||||
if not release or release.status != "PUBLISHED": errors.append("Selecione uma versão publicada do Agent.")
|
||||
if errors:
|
||||
tenants = session.scalars(select(Tenant).order_by(Tenant.name)).all()
|
||||
tenants = _active_gerenciador_tenants(session)
|
||||
releases = session.scalars(select(AgentRelease).where(AgentRelease.status == "PUBLISHED").order_by(AgentRelease.created_at.desc())).all()
|
||||
return render(request, "agent_new_installation.html", {"tenants": tenants, "releases": releases, "auth": auth_data, "error": " ".join(errors)}, 400)
|
||||
return render(request, "agent_new_installation.html", {"tenants": tenants, "releases": releases, "auth": auth_data, "error": " ".join(errors), "search": ""}, 400)
|
||||
enrollment_uuid = str(uuid.uuid4())
|
||||
enr = AgentEnrollment(enrollment_uuid=enrollment_uuid, tenant_id=tenant.id, display_name=display_name, expected_hostname=expected_hostname, release_id=release.id, status="PENDING", created_by=admin.id)
|
||||
session.add(enr)
|
||||
|
||||
Reference in New Issue
Block a user