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:
gti
2026-09-23 22:38:58 -03:00
co-authored by Claude Sonnet 5
parent ac7b503c2f
commit e7d77beb26
29 changed files with 453 additions and 38 deletions
+12 -2
View File
@@ -6,12 +6,14 @@
{% if error %}<p class="error" role="alert">{{ error }}</p>{% endif %}
{% if not releases %}
<p class="muted">Nenhuma versão do Agent está publicada ainda. Publique uma release primeiro (<code>python central/scripts/publish_agent_release.py</code>) e ela aparecerá aqui.</p>
{% elif not tenants %}
<p class="muted">Nenhum cliente ativo encontrado no GTI Gerenciador. Verifique o cadastro por lá, ou rode a sincronização (<code>python central/scripts/sync_clients_from_gerenciador.py</code>).</p>
{% else %}
<form method="post" action="/agents/new">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Cliente
<select name="tenant_id" required>
<option value="">Selecione</option>
<input type="text" id="filtro-cliente" placeholder="Buscar cliente..." autocomplete="off" oninput="filtrarClientes(this.value)">
<select name="tenant_id" id="select-cliente" size="8" required>
{% for tenant in tenants %}<option value="{{ tenant.id }}">{{ tenant.name }}</option>{% endfor %}
</select>
</label>
@@ -28,6 +30,14 @@
</label>
<p><button type="submit">Criar instalação</button></p>
</form>
<script>
function filtrarClientes(termo) {
var alvo = termo.trim().toLowerCase();
document.querySelectorAll("#select-cliente option").forEach(function (opcao) {
opcao.hidden = alvo.length > 0 && opcao.textContent.toLowerCase().indexOf(alvo) === -1;
});
}
</script>
{% endif %}
</section>
{% endblock %}
+1 -1
View File
@@ -35,7 +35,7 @@
<section class="panel">
<h2>Detalhes</h2>
<table>
<tr><th>Cliente</th><td>{{ tenant.name if tenant else "-" }}</td></tr>
<tr><th>Cliente</th><td>{{ tenant.name if tenant else "-" }}{% if tenant and not tenant.active %} <span class="badge critical">INATIVO</span>{% endif %}</td></tr>
<tr><th>Nome</th><td>{{ enr.display_name }}</td></tr>
<tr><th>Hostname esperado</th><td>{{ enr.expected_hostname or "não informado" }}</td></tr>
<tr><th>Versão do Agent</th><td>{{ release.version if release else "-" }} ({{ release.platform if release else "" }}/{{ release.architecture if release else "" }})</td></tr>