# Feature Specification: Domínios VM112 — Purge & Histórico (017) **Criado:** 2026-06-16 **Actualizado:** 2026-06-19 (extensões Spec 026 · Spec 032) **Solicitado por:** Roger **Status:** v1 + v2 concluídos · Fase 3 VM112 pendente **Prioridade:** P1 (testes E2E + padrão de limpeza) **Sistema:** Desk VM122 + Wizard VM112 **Módulo:** `vm112-domains` **UI purge:** página **Serviços** (Spec 018) **UI histórico:** **Eventos → Histórico de purges** --- ## Resumo Técnicos **Admin** (`super_admin`, `ops_lead`) executam **purge completo** de domínios VM112 (Carbonio, site, portal, Cloudflare, Traefik/SNI, registos Desk) a partir da página **Serviços**, com timeline ao vivo no drawer lateral. **v2 (2026-06-16):** cada purge fica **persistido** em SQLite e consultável em **Eventos → Histórico de purges** — lista clicável + modal com timeline, utilizador e serviços removidos. **Uso inicial:** limpar domínios de teste para reentrarem no wizard. **Futuro:** padrão de limpeza de dados por domínio. --- ## Módulo Desk (Spec 015) | Campo | Valor | |-------|--------| | `id` | `vm112-domains` | | `label` | Domínios VM112 | | `default_enabled` | `true` | | `nav_views` | _(vazio — purge na página Serviços, histórico em Eventos)_ | --- ## RBAC | Acção | Perfis | |-------|--------| | Executar purge (Serviços) | `super_admin`, `ops_lead` + **senha Root** | | Ver histórico de purges (Eventos) | `super_admin`, `ops_lead` | | Listar / detalhe job purge | `super_admin`, `ops_lead` | Técnicos `technician` e `noc` **não** acedem. --- ## UI — Serviços (Spec 018) ### Tile E-mail Tenant → modal purge 1. **Resumo** — domínio, mail host, admin portal, contas Carbonio, zona CF 2. **Infra** — passos `get_status()` (Carbonio, DNS, SNI, Traefik) 3. **Contas** — lista e-mails Carbonio 4. **Zona perigosa — Purge** (Admin only) - Aviso irreversível - Confirmação: digitar domínio exacto - Campo **senha Root** (Desk) - Botão «Apagar domínio e todos os dados» 5. **Drawer lateral** `vm112-purge-drawer` — timeline em tempo real durante execução --- ## API Desk (VM122) | Método | Path | Descrição | |--------|------|-----------| | GET | `/api/v1/vm112/domains?q=` | Lista domínios orquestrados (proxy VM112) | | GET | `/api/v1/vm112/domains/{domain}` | Detalhe + infra status | | POST | `/api/v1/vm112/domains/{domain}/purge/jobs` | **Recomendado** — purge async + polling | | GET | `/api/v1/vm112/purge/jobs/{job_id}` | Estado / timeline do job | | POST | `/api/v1/vm112/purge/jobs/{job_id}/recover` | Recuperar job após timeout UI | | GET | `/api/v1/vm112/purge/jobs?limit=&offset=` | **v2** — lista histórico persistido | | POST | `/api/v1/vm112/domains/{domain}/purge/stream` | Purge SSE (legado Traefik) | | POST | `/api/v1/vm112/domains/{domain}/purge` | Purge síncrono (legado) | **Body purge:** ```json { "confirm_domain": "iofficebooks.com", "root_password": "********" } ``` **Validações purge:** 1. `user.role` ∈ {super_admin, ops_lead} 2. `verify_password(root_password, hash do user root)` 3. `confirm_domain` === domínio (case-insensitive) 4. Domínio ∉ blocklist (`ligbox.com.br`, etc.) 5. Proxy VM112 `POST /api/admin/domains/{domain}/purge` com `X-Api-Key` **Pós-purge Desk:** apagar `audit_domains`, `webhook_events`, `tickets`, `assist_sessions`, `audit_checks` com referência ao domínio. --- ## API VM112 | Método | Path | Auth | |--------|------|------| | GET | `/api/admin/domains` | `X-Api-Key` | | GET | `/api/admin/domains/{domain}` | `X-Api-Key` | | POST | `/api/admin/domains/{domain}/purge` | `X-Api-Key` | | GET | `/api/admin/domains/purge-jobs/{job_id}` | `X-Api-Key` _(memória, efémero)_ | **Purge VM112 (ordem):** 1. Apagar contas Carbonio (`zmprov da`) 2. Apagar domínio Carbonio (`zmprov dd`) 3. Remover portal users com `planned_corporate_email` no domínio 4. Apagar `/opt/ligbox-sites/domains/{domain}/` 5. Apagar zona Cloudflare (se existir na conta Ibytera) 6. Remover `mail.{domain}` do SNI + routers Traefik (CT114) 7. Apagar logs sessão JSONL com referência ao domínio --- ## Fase 2 — Jobs async + polling (implementado) `POST /api/v1/vm112/domains/{domain}/purge/jobs` inicia thread em background. UI faz polling `GET /api/v1/vm112/purge/jobs/{id}` a cada 2s. **Motivo:** SSE longo falhava via Traefik (`504` / `Failed to fetch` ~60–79s). **Fix nginx Desk:** `proxy_read_timeout 600s` em `frontend/nginx.conf`. Persistência SQLite (`vm112_purge_jobs`) criada nesta fase — base para v2. --- ## Fase 2 — SSE (implementado, legado) `POST /api/v1/vm112/domains/{domain}/purge/stream` · `text/event-stream` | type | Conteúdo | |------|----------| | `step` | `{ label, at, status, detail }` | | `heartbeat` | `{ elapsed }` — cada 5s | | `error` | purge falhou | | `done` | `{ desk, vm112, domain }` | Ordem: validação → VM112 (heartbeat) → passos VM112 → passos Desk → concluído. --- ## v2 — Histórico de purges (implementado 2026-06-16) ### Problema resolvido | Antes | Depois | |-------|--------| | Timeline só ao vivo no drawer | Histórico persistente no Desk | | Dados em SQLite sem UI | Lista + modal de detalhe | | VM112 jobs em memória (efémero) | Fonte de verdade: VM122 `ops.db` | | Purges «desapareciam» ao fechar modal | Consulta por domínio, data, utilizador | **Nota:** purges **antes** da persistência (ex.: `betinsport.com`) não aparecem no histórico. ### UI — Eventos - Aba **Webhooks** (existente) - Aba **Histórico de purges** (Admin only) - Lista: Job ID, domínio, status, utilizador, resumo Desk, data, duração VM112 - Clique na linha → modal com: 1. Cabeçalho (domínio, status, utilizador, data, job id) 2. Removido no Desk — webhook_events, tickets, audit_domains, assist_sessions, audit_checks 3. Removido na VM112 — Carbonio, portal, site, Cloudflare, Traefik 4. Timeline completa (`timeline_json`) ### Persistência | Campo | Valor | |-------|--------| | Base | `/var/lib/ligbox-ops-platform/ops.db` (Docker: `/data/ops.db`) | | Tabela | `vm112_purge_jobs` | | Colunas | `timeline_json`, `desk_json`, `vm112_json`, `by_user`, `status`, `created_at` | ### Ficheiros v2 | Ficheiro | Alteração | |----------|-----------| | `api/app/vm112_purge_jobs.py` | `list_jobs()`, schema, persistência | | `api/app/vm112_domains_routes.py` | `GET /purge/jobs` | | `frontend/assets/app.js` | `renderPurgeHistory()`, modal, aba Eventos | | `frontend/index.html` | Toolbar Eventos + `purge-history-modal` | | `frontend/assets/styles.css` | Estilos lista/modal | ### Critérios de aceitação v2 1. Admin vê aba «Histórico de purges» em Eventos. 2. Lista mostra purges com status, utilizador, data e resumo Desk. 3. Clique abre modal com timeline completa e contagens por serviço. 4. Badges correctos: `done`, `error`, `running`, `queued`. 5. `technician` / `noc` não vêem a aba. ### Consulta manual (SSH VM122) ```bash sqlite3 /var/lib/ligbox-ops-platform/ops.db \ "SELECT id, domain, status, by_user, created_at FROM vm112_purge_jobs ORDER BY created_at DESC;" ``` ```bash curl -s -H "Authorization: Bearer $TOKEN" \ "https://desk.ligbox.com.br/api/v1/vm112/purge/jobs/57845ca1c5c64b53" ``` --- ## Extensão — Spec 026 (purge Traefik validation) Validação YAML + smoke onboard pós-remoção Traefik (CT114). Incidente 2026-06-19: `dynamic.yml` inválido após purge → 404 global no onboard. **Spec dedicada:** `specs/026-purge-traefik-validation/spec.md` --- ## Extensão — Spec 032 (purge autorização extra) Domínios em `PURGE_EXTRA_AUTH_DOMAINS` (ex.: `myvexx.com`) exigem **código de autorização** gerado pelo root em **Infra** (senha Root), além da validação purge normal. | Camada | Regra | |--------|--------| | Blocklist | `ligbox.com.br`, `itecnologys.com` — purge proibido | | Extra auth | Código único + senha Root | | Normal | Senha Root (Spec 017 v1) | **Spec dedicada:** `specs/032-purge-domain-extra-auth/spec.md` **UI geração:** Infraestrutura → «Códigos autorização purge» **UI consumo:** Serviços → modal purge (campo código) --- ## Fase 3 — VM112 passos em tempo real (pendente) VM112 (`/opt/ligbox-wizard`) emitir passos individuais durante execução (Carbonio, CF, Traefik) em vez de bloco único + heartbeat. Alterações no wizard, não só no Desk. --- ## Critérios de aceitação (v1) 1. Admin executa purge a partir de Serviços. 2. Purge com senha root errada → erro na timeline. 3. Purge com domínio confirmado errado → HTTP 400. 4. Após purge, domínio ausente em Carbonio, ligbox-sites e Desk. 5. Drawer mostra progresso ao vivo; job persiste em SQLite. --- ## Fora de escopo - Purge parcial (só contas, só DNS) - Scheduler de limpeza automática - Export CSV/PDF do histórico - Filtro por domínio/data na lista de histórico - Retenção automática / purge de jobs antigos - Link directo Serviços → histórico do domínio --- ## Conclusão (2026-06-16) A Spec 017 cobre o ciclo completo de purge de domínio VM112: | Fase | Entrega | Estado | |------|---------|--------| | v1 | Purge completo via Serviços + validação Root | ✅ | | Fase 2 | Jobs async, polling, persistência SQLite | ✅ | | Fase 2 SSE | Timeline drawer (legado) | ✅ | | **v2** | Histórico em Eventos — lista + modal audit trail | ✅ | | **032** | Códigos autorização extra (myvexx.com) — Infra + Serviços | ✅ | | **026** | Validação Traefik pós-purge | ✅ | | Fase 3 | Passos VM112 em tempo real no wizard | ⏳ | **Purges registados (exemplo):** `myvexx.com`, `diarissima.com`, `ibytera.com` — visíveis em Eventos → Histórico de purges. **Próximo passo natural:** Fase 3 no wizard VM112; depois filtros/export no histórico se necessário.