Pular para o conteúdo principal

Portal de Controle

O Portal de Controle (subsistema 2) é uma SPA (React + Vite + TailwindCSS) que roda no mesmo processo/porta da API — não é um serviço separado. É buildada (panel/panel/dist) e servida como arquivos estáticos pelo próprio Fastify, com fallback de SPA para o roteamento client-side.

  • Acesso: http://<host>:<porta>/panel (ex.: http://localhost:8080/panel em dev, ou o domínio configurado no EasyPanel em produção).
  • Autenticação: login por e-mail/senha (POST /auth/login), que devolve um JWT guardado no navegador e enviado em Authorization: Bearer <token> nas chamadas seguintes. O WebSocket de eventos em tempo real recebe o mesmo JWT via querystring, já que o navegador não envia headers customizados no handshake do WebSocket.
  • A apikey global continua funcionando em paralelo, exatamente como antes do portal existir — JWT e apikey são dois métodos de autenticação aceitos pelo mesmo hook (src/server.ts), nenhuma integração existente precisa migrar.

Telas

Dashboard

Tela inicial após o login: KPIs agregados (instâncias, envios do dia), saúde por instância (verde/amarelo/vermelho) e alertas não lidos. Alimentada por GET /dashboard/summary.

Instâncias

Lista todas as instâncias, com criação de instância nova, conectar (abre um diálogo com o QR Code ao vivo), logout e exclusão. Cada linha leva para a tela de Detalhe.

Detalhe da instância

Cinco abas, todas operando sobre a instância selecionada (panel/src/pages/InstanciaDetalhe.tsx):

  1. Anti-ban — formulário de configuração do AntiBanGuard (janela de horário, limite diário, opt-in obrigatório, warmup).
  2. Métricas — gráficos de volume, taxa de resposta e bloqueios/denúncias por dia.
  3. Webhooks — URL e eventos assinados para a instância, com log de entregas recentes.
  4. Envio de teste — formulário para enviar uma mensagem de teste (com opção de marcar como envio proativo, para exercitar os gates do anti-ban).
  5. Blacklist — consulta e gestão da blacklist da instância, e registro manual de bloqueio/denúncia (POST /instance/:name/report-block) — o único jeito de alimentar os contadores blocks/reports que disparam o circuit breaker vermelho, já que a API não-oficial do WhatsApp Web não expõe esse evento.

Usuários

CRUD de usuários do portal (e-mail, nome, senha, papel, ativo/inativo). Tela admin-only — só aparece no menu e só é acessível para quem tem role: admin (a trava de UX é só conveniência; a trava real é no servidor, ver Matriz de papéis).

Alertas

Lista de alertas gerados pelo sistema (ex.: circuit breaker aberto para uma instância) e marcação de leitura. Visível para todos os usuários, não só admin — é assim que um operador descobre que uma instância que ele opera foi pausada pelo kill switch.

Primeiro admin (seed automático)

No primeiro boot da aplicação, se a tabela de usuários estiver vazia (zero linhas) e as variáveis ADMIN_EMAIL e ADMIN_PASSWORD estiverem definidas no ambiente, um usuário administrador é criado automaticamente com essas credenciais.

Não existe senha padrão fixa no código

Sem ADMIN_EMAIL/ADMIN_PASSWORD configuradas no primeiro boot, nenhum admin é semeado automaticamente — será preciso criar o primeiro usuário manualmente (inserção direta no banco). Depois que já existir ao menos um usuário na tabela, o seeder nunca mais roda: essas variáveis podem ser removidas do ambiente após o primeiro boot em produção, sem efeito nos logins seguintes.

Matriz de papéis: admin × operador

Todo usuário do portal tem um papel (role): admin ou operador. O controle de acesso por papel é verificado no servidor (exigirAdmin, src/server.ts) — não é só uma trava visual do frontend (a trava de UX em RequireAdmin, panel/src/router.tsx, existe apenas para não mostrar uma tela que o operador não pode usar; a fronteira de segurança real é o servidor).

exigirAdmin está registrado como preHandler em exatamente cinco rotas: a exclusão de instância e as quatro rotas de CRUD de usuários.

AçãoAdminOperador
Criar instância (POST /instance/create)
Conectar / ver QR Code (GET /instance/connect/:name)
Logout de instância (DELETE /instance/logout/:name)
Deletar instância (DELETE /instance/delete/:name)❌ (403)
Editar configuração anti-ban (PUT /instance/:name/antiban)
Envio de teste (POST /message/sendText etc.)
Registrar bloqueio/denúncia (POST /instance/:name/report-block)
Configurar webhook (PATCH /instance/:name/webhook)
Ver alertas / marcar como lido (/alertas*)
Ver dashboard e métricas
Gestão de usuários (GET/POST/PATCH/DELETE /auth/users*)❌ (403)

Resumindo: admin-only é deletar instância e todo o CRUD de usuários (/auth/users*) — o resto (criar, conectar/QR, logout, editar anti-ban, envio de teste, report-block, configurar webhook, ver alertas/dashboard) o operador também pode fazer.

Requisições por apikey contam como admin

Requisições autenticadas pela apikey global (integração servidor-a-servidor, sem JWT/usuário do portal) são tratadas como tendo poder total — a apikey é a credencial de máquina, equivalente a admin, e não passa pela distinção de papéis do portal.

Próximos passos

  • Primeiros passos — o mesmo fluxo de criar instância e enviar a primeira mensagem, só que pelo portal em vez de cURL.
  • Deploy no EasyPanel — como colocar o portal no ar em produção, incluindo as variáveis JWT_SECRET/ADMIN_EMAIL/ADMIN_PASSWORD.