feat(frontend): Monitoramento em tempo real (WebSocket via proxy SSE)
O RealtimeGateway (backend, desde a PHASE 13) autentica a conexão socket.io via auth.token no handshake — um JWT bruto que um EventSource/WebSocket do browser não tem como mandar sem passar por JS legível no client, quebrando o princípio seguido em todo o resto do frontend (token só existe no cookie httpOnly). Resolvido com um proxy: apps/frontend/src/app/api/monitoring/stream/route.ts roda no servidor, conecta no socket.io real com o access token do lado do servidor, e reencaminha cada evento pro browser como Server-Sent Events — o EventSource do client só precisa do cookie de sessão, nunca do token. /app/monitoramento: badge de conexão, contadores de sessão (chamadas criadas/atendidas/encerradas), filas ao vivo (QUEUE_MEMBER_COUNT), estado de agentes ao vivo (AGENT_STATE_CHANGED, semeado do GET /agents inicial), feed dos últimos 50 eventos. Menu "Monitoramento" vira 1 link direto em vez de 5 sub-itens placeholder — o painel novo já cobre tudo numa página só. Testado ponta a ponta contra o pipeline real (não simulado): cliente socket.io cru confirmou a API entregando o evento, curl -N confirmou o proxy reencaminhando, e com a página aberta de verdade num browser (Puppeteer) disparei POST /agents/me/login e /logout por fora — o badge do agente mudou ao vivo e os eventos apareceram no feed sem recarregar a página. Smoke test de regressão nas 19 telas anteriores, todas 200. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
63
TODO.md
63
TODO.md
@@ -1113,6 +1113,69 @@ Troncos, Discador > Lista de Bloqueio (agente.md secao 50-51, 41-42, 71,
|
||||
decisão de escopo pra manter o formulário enxuto nesta primeira
|
||||
versão
|
||||
|
||||
## PHASE 32 — Frontend: Monitoramento em tempo real
|
||||
(agente.md secao 54-55, 161)
|
||||
- [x] **Achado de arquitetura, resolvido antes de codar**: o
|
||||
`RealtimeGateway` (backend, já existia desde a PHASE 13) autentica a
|
||||
conexão socket.io via `auth.token` no handshake — um JWT bruto. Um
|
||||
`EventSource`/`WebSocket` do browser não tem como mandar esse token
|
||||
sem ele passar por JS legível no client, quebrando o mesmo princípio
|
||||
seguido em todo o resto do frontend (o access token só existe no
|
||||
cookie httpOnly). Resolvido com um proxy: `apps/frontend/src/app/
|
||||
api/monitoring/stream/route.ts` roda no servidor (runtime Node.js),
|
||||
conecta no socket.io real com o access token do lado do servidor
|
||||
(`socket.io-client`, dependência nova só usada aqui), e reencaminha
|
||||
cada evento pro browser como Server-Sent Events — o `EventSource` do
|
||||
client só precisa do cookie de sessão (same-origin, automático),
|
||||
nunca do token. Como bônus, elimina qualquer necessidade de mexer em
|
||||
`CORS_ORIGIN` (a conexão socket.io real é servidor-servidor, nunca
|
||||
passa pelo browser).
|
||||
- [x] `/app/monitoramento`: badge de conexão (Conectando/Ao vivo/
|
||||
Reconectando), 3 contadores de sessão (chamadas criadas/atendidas/
|
||||
encerradas, zeram a cada reload — não são um total histórico),
|
||||
grade de filas ao vivo (contagem de espera via `QUEUE_MEMBER_COUNT`,
|
||||
traço fantasma até o primeiro evento — sem endpoint de "contagem
|
||||
atual" pra semear um valor inicial), lista de agentes com estado ao
|
||||
vivo (semeada do `GET /agents` inicial, atualizada via
|
||||
`AGENT_STATE_CHANGED`), e feed dos últimos 50 eventos com descrição
|
||||
legível por tipo.
|
||||
- [x] Menu Tenant: "Monitoramento" deixa de ter 5 sub-itens placeholder
|
||||
(Campanhas/Filas/Agentes/Ramais/Troncos) e vira 1 link direto — o
|
||||
painel novo já cobre filas+agentes+eventos gerais numa página só;
|
||||
abrir em 5 rotas separadas duplicaria a conexão SSE sem necessidade
|
||||
real (mesma decisão já tomada nas Relatórios: 1 `href` por item de
|
||||
menu, nunca vários apontando pro mesmo lugar).
|
||||
- [x] `agent`/`queue` do mod_callcenter chegam como `"<id>@<domain>"`
|
||||
(secao 45, 50) — `stripDomain()` novo em `lib/realtime-types.ts`
|
||||
separa o id antes de resolver nome; `AGENT_STATE_CHANGED` (publicado
|
||||
pela própria API, não pelo FreeSWITCH) já vem com `agentId` cru, sem
|
||||
sufixo.
|
||||
- [x] Testado ponta a ponta contra o pipeline real (não simulado): um
|
||||
cliente socket.io cru confirmou primeiro que a API já entrega o
|
||||
evento certo (`AGENT_STATE_CHANGED`) ao vivo; a mesma verificação
|
||||
via `curl -N` direto no proxy SSE confirmou o reencaminhamento;
|
||||
depois, com a página `/app/monitoramento` aberta de verdade num
|
||||
browser (Puppeteer), disparado `POST /agents/me/login` e
|
||||
`/logout` por fora — o badge do agente mudou de Offline pra
|
||||
"Disponível" e voltou, e os dois eventos apareceram no feed ao
|
||||
vivo, tudo em tempo real sem recarregar a página. Smoke test de
|
||||
regressão nas 19 telas anteriores do tenant + platform, todas 200
|
||||
(a rota de monitoramento mantém uma conexão aberta de propósito,
|
||||
então usa `domcontentloaded` em vez de `networkidle0` no teste).
|
||||
- [ ] Sem reconciliação/snapshot ao conectar (secao 55: "estado inicial
|
||||
completo, não só eventos a partir de agora") — filas começam sem
|
||||
dado até o primeiro `QUEUE_MEMBER_COUNT`; agentes começam certos
|
||||
porque semeiam do `GET /agents` inicial, mas filas não têm
|
||||
equivalente (nenhum endpoint retorna "contagem atual" fora do
|
||||
próprio stream de eventos)
|
||||
- [ ] Sem monitoramento de ramais (registro/busy, secao 55 completa) nem
|
||||
de campanhas/troncos em tempo real — cobertos só pelos relatórios
|
||||
de período, não por esta tela
|
||||
- [ ] Reconexão do `EventSource` é o comportamento padrão do browser
|
||||
(tenta de novo sozinho); o lado do servidor limita a 5 tentativas de
|
||||
reconexão do socket.io real antes de fechar o stream, mas não há
|
||||
backoff exponencial nem um teste de queda de rede prolongada
|
||||
|
||||
---
|
||||
|
||||
## Riscos conhecidos
|
||||
|
||||
Reference in New Issue
Block a user