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/panelem 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 emAuthorization: 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
apikeyglobal continua funcionando em paralelo, exatamente como antes do portal existir — JWT eapikeysã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):
- Anti-ban — formulário de configuração do
AntiBanGuard(janela de horário, limite diário, opt-in obrigatório, warmup). - Métricas — gráficos de volume, taxa de resposta e bloqueios/denúncias por dia.
- Webhooks — URL e eventos assinados para a instância, com log de entregas recentes.
- 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).
- 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 contadoresblocks/reportsque 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.
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ção | Admin | Operador |
|---|---|---|
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 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.