feat: pagina do gerador de chave manual (client-side, sem gravar no banco)
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
Calculadora de chave de reativação, para uso manual (reenvio/correção
|
||||
pontual). 100% client-side (mesmo algoritmo gerarReativacao já usado no
|
||||
workflow n8n e em Gerar chave WEB/index.html) — esta rota só serve a
|
||||
página; NUNCA grava em Clientes_Chaves.
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from ..auth import exigir_login
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
|
||||
|
||||
@router.get("/gerador-chave", response_class=HTMLResponse)
|
||||
def pagina(request: Request, usuario: str = Depends(exigir_login)):
|
||||
return templates.TemplateResponse(request, "gerador_chave.html", {})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,85 @@
|
||||
{% extends "base.html" %}
|
||||
{% block titulo %}Gerador de Chave de Reativação{% endblock %}
|
||||
{% block conteudo %}
|
||||
<div style="max-width:420px;margin:0 auto;">
|
||||
<img src="/static/logo-gti-256x256.png" alt="GTI Grupo" style="width:72px;height:72px;border-radius:14px;display:block;margin:0 auto 16px;">
|
||||
<h1 style="text-align:center;">Gerador de Chave de Reativação</h1>
|
||||
|
||||
<label for="codigo">Código do cliente</label>
|
||||
<input type="text" id="codigo" placeholder="Ex: 000123" maxlength="6" inputmode="numeric">
|
||||
|
||||
<label for="referencia">Mês/ano de referência</label>
|
||||
<input type="month" id="referencia" min="2000-01" max="2100-12">
|
||||
|
||||
<button id="gerar">Gerar chave</button>
|
||||
|
||||
<div id="erro" style="color:#f87171;margin-top:14px;display:none;"></div>
|
||||
<div id="resultado" style="margin-top:20px;padding:14px;border-radius:8px;background:#17171a;border:1px solid #3a3a3e;border-left:3px solid #F5821F;word-break:break-all;font-family:'Courier New',monospace;color:#F5821F;display:none;"></div>
|
||||
<button id="copiar" style="display:none;background:transparent;border:1px solid #3a3a3e;color:#a1a1aa;width:auto;padding:6px 10px;">Copiar</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const CHAVE_INTERNA =
|
||||
"YUQL23KL23DF90WI5E1JAS467NMCXXL6JAOAUWWMCL0AOMM4A4VZYW9KHJUI2347" +
|
||||
"EJHJKDF3424SKL K3LAKDJSL9RTIKJ";
|
||||
|
||||
function gerarReativacao(codigoCliente, mes, ano, byteInicial = null) {
|
||||
const cliente = String(codigoCliente).padStart(6, '0');
|
||||
const texto = String(mes).padStart(2, '0') + cliente + String(ano).slice(-2);
|
||||
const aleatorio = byteInicial !== null ? byteInicial : Math.floor(Math.random() * 256);
|
||||
let anterior = aleatorio;
|
||||
const resultado = [aleatorio];
|
||||
const bytes = [];
|
||||
for (let i = 0; i < texto.length; i++) bytes.push(texto.charCodeAt(i) & 0xff);
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
const valor = (bytes[i] + anterior) % 255;
|
||||
const cifrado = valor ^ CHAVE_INTERNA.charCodeAt(i % CHAVE_INTERNA.length);
|
||||
resultado.push(cifrado);
|
||||
anterior = cifrado;
|
||||
}
|
||||
return resultado.map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('');
|
||||
}
|
||||
|
||||
const $codigo = document.getElementById('codigo');
|
||||
const $referencia = document.getElementById('referencia');
|
||||
const $erro = document.getElementById('erro');
|
||||
const $resultado = document.getElementById('resultado');
|
||||
const $copiar = document.getElementById('copiar');
|
||||
|
||||
(function preencherMesAtual() {
|
||||
const hoje = new Date();
|
||||
$referencia.value = `${hoje.getFullYear()}-${String(hoje.getMonth() + 1).padStart(2, '0')}`;
|
||||
})();
|
||||
|
||||
function mostrarErro(msg) {
|
||||
$erro.textContent = msg;
|
||||
$erro.style.display = 'block';
|
||||
$resultado.style.display = 'none';
|
||||
$copiar.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('gerar').addEventListener('click', () => {
|
||||
$erro.style.display = 'none';
|
||||
const codigoCliente = String($codigo.value || '').trim();
|
||||
if (!/^[0-9]{1,6}$/.test(codigoCliente)) return mostrarErro('Código do cliente deve ter até 6 dígitos numéricos.');
|
||||
const codigoFormatado = codigoCliente.padStart(6, '0');
|
||||
if (!/^\d{4}-\d{2}$/.test($referencia.value)) return mostrarErro('Selecione o mês/ano de referência.');
|
||||
const [anoStr, mesStr] = $referencia.value.split('-');
|
||||
const mesNum = Number(mesStr), anoNum = Number(anoStr);
|
||||
if (!(mesNum >= 1 && mesNum <= 12)) return mostrarErro('Mês fora do intervalo 1-12.');
|
||||
if (!(anoNum >= 2000 && anoNum <= 2100)) return mostrarErro('Ano inválido (use entre 2000 e 2100).');
|
||||
|
||||
const chave = gerarReativacao(codigoFormatado, mesNum, anoNum);
|
||||
$resultado.textContent = chave;
|
||||
$resultado.style.display = 'block';
|
||||
$copiar.style.display = 'inline-block';
|
||||
});
|
||||
|
||||
$copiar.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText($resultado.textContent).then(() => {
|
||||
$copiar.textContent = 'Copiado!';
|
||||
setTimeout(() => { $copiar.textContent = 'Copiar'; }, 1500);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.auth import exigir_login
|
||||
from app.routers import chave_manual as router_chave_manual
|
||||
|
||||
app_teste = FastAPI()
|
||||
app_teste.include_router(router_chave_manual.router)
|
||||
app_teste.dependency_overrides[exigir_login] = lambda: "teste"
|
||||
client = TestClient(app_teste)
|
||||
|
||||
|
||||
def test_pagina_gerador_carrega():
|
||||
resposta = client.get("/gerador-chave")
|
||||
assert resposta.status_code == 200
|
||||
assert "gerarReativacao" in resposta.text
|
||||
assert "Gerador de Chave de Reativação" in resposta.text
|
||||
|
||||
|
||||
def test_pagina_gerador_nao_grava_nada():
|
||||
# Rota é GET-only: não existe POST /gerador-chave, então não há como
|
||||
# este router gravar em Clientes_Chaves.
|
||||
resposta = client.post("/gerador-chave")
|
||||
assert resposta.status_code == 405
|
||||
Reference in New Issue
Block a user