# Spec 037-DNS-VIEWER — Painel DNS read-only unificado **Criado:** 2026-06-25 **Solicitado por:** Roger **Status:** 📋 Especificado — implementação Fase 1 parcial (Desk CF only) **Prioridade:** P1 **Depende de:** [spec.md](./spec.md) · [004 cloudflare-zone-provision](../004-onboard-funnel-events/cloudflare-zone-provision.md) · [009](../009-ops-audit-overview/spec.md) · [028](../028-openpanel-ce-ligbox-reengineering/DNS53_OPENPANEL_PORTA53.md) · [035 §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md) · [027 RBAC](../027-desk-rbac-function-matrix/spec.md) --- ## 1. Objectivo Permitir que **staff Ligbox** e **gerente de domínio** vejam **todos os apontamentos DNS** relevantes para um domínio — **sem alterar** registos na UI — com: 1. **Lista completa read-only** (tipo, nome, conteúdo, TTL, função) 2. **Origem do DNS** (Cloudflare Ligbox · BYO · OpenPanel BIND · registrador externo · resolução pública) 3. **Link «Editar aqui»** para a consola correcta (Cloudflare, OpenPanel, registrador) 4. **Regra wizard:** se o cliente escolhe **trazer DNS para Ligbox** → mostrar o que **será / foi aplicado**; caso contrário → mostrar **onde o DNS está no momento** da operação **Fora de scope:** PATCH/POST/DELETE de registos no Desk ou Console — edição só via deep-link externo. --- ## 2. Problema actual (Roger 2026-06-25) | Superfície | O que existe hoje | Lacuna | |------------|-------------------|--------| | Desk Overview → modal domínio | Tabela CF via `GET /api/v1/dns/cloudflare/records` | Só Cloudflare; sem OpenPanel; sem link editar | | Desk Serviços IaaS | Resumo zona CF | Sem tabela de registos | | Wizard passo DNS | Modos Ligbox/BYO/Registrador (037) | Cliente vê passos; staff não tem painel unificado | | OpenPanel BIND | Zonas em `openpanel_dns` VM123 | **Zero UI Desk/Console** — operador não sabe apontamentos | | Console `/admin` §5.5 | MX/NS status (mock) | Sem lista completa | --- ## 3. Regra de produto — caminho Ligbox vs externo Esta regra **obrigatória** aplica-se em **Wizard**, **Desk** e **Console `/admin`**. ```mermaid flowchart TD A[Domínio + sessão onboarding] --> B{dns_mode escolhido?} B -->|ligbox_cf_*| C[Modo PLANEADO / APLICADO] B -->|byo_cf| D[Modo BYO — zona cliente CF] B -->|external / registrador| E[Modo ACTUAL — DNS público] B -->|openpanel_bind| F[Modo OPENPANEL — zona BIND VM123] C --> C1[Mostrar NS Cloudflare Ligbox] C --> C2[Mostrar mail_dns_records preview] C --> C3[Após apply: registos CF reais] C --> C4[Link: Cloudflare conta Ligbox] D --> D1[Mostrar registos zona BYO via token vault] D --> D2[Link: dash.cloudflare.com zona cliente] E --> E1[GET dns/instructions — o que falta colar] E --> E2[GET dns/verify + dig público 009] E --> E3[NS actuais no registrador] E --> E4[Link: registrador ou painel CF cliente] F --> F1[Listar zona BIND OpenPanel] F --> F2[Link: OpenPanel DNS UI] ``` ### 3.1 Tabela de decisão UI | `dns_mode` (persistido) | Badge UI | Fonte de dados | O que mostrar | |-------------------------|----------|----------------|---------------| | `ligbox_cf_ligit` / `itecnologys` / `ibytera` | **DNS Ligbox** | CF API conta Ligbox + `mail_dns_records()` | Registos **aplicados**; se pré-NS → **preview** + NS a configurar | | `ligbox_cf_provision_pending` | **DNS Ligbox (aguarda NS)** | `provision-zone` + preview | NS Cloudflare + tabela «será aplicado após NS» | | `byo_cf` | **Cloudflare cliente** | Token BYO vault + CF API | Registos actuais na zona BYO | | `external` / `registrar` | **DNS externo** | `dns/instructions` + `dig` público | Estado **actual** + instruções manuais | | `openpanel_bind` | **OpenPanel BIND** | OpenAdmin API / zone file VM123 | Registos autoritativos `:53` Ligbox | | `unknown` | **A determinar** | `dns/resolve` + NS lookup | NS actuais + sugestão de caminho | ### 3.2 Texto UX (gerente / staff) | Modo | Mensagem principal | |------|-------------------| | Ligbox (pré-apply) | «Estes apontamentos **serão configurados** na Cloudflare Ligbox quando confirmar o passo DNS.» | | Ligbox (pós-apply) | «Apontamentos **activos** na Cloudflare Ligbox.» | | Externo | «O domínio usa DNS **fora da Ligbox**. Abaixo: o que está **publicamente** resolvido agora.» | | OpenPanel | «Zona servida pelo **DNS Ligbox (OpenPanel)** em `95.216.14.162`.» | --- ## 4. Fontes de dados (backend) ### 4.1 Matriz de providers | Provider ID | Quando usar | API / método | Edit link template | |-------------|-------------|--------------|-------------------| | `cf_ligbox` | Zona numa conta Ligbox (037) | CF API token conta + `zone_id` | `https://dash.cloudflare.com/{account_id}/{zone_name}/dns` | | `cf_byo` | BYO wizard | Token vault sessão / entitlements | `https://dash.cloudflare.com/` (zona cliente) | | `openpanel_bind` | Subdomínio/site bundle OP | OpenAdmin `GET /api/domains/{domain}/dns` ou `rndc`/zone export | `https://openpanel.ligbox.com.br/domains/{domain}/dns` | | `public_resolver` | Sempre (fallback / externo) | `dig` + `dns/verify` VM112 | — | | `planned_ligbox` | Ligbox antes de apply | `mail_dns_records(domain)` wizard | — (preview only) | ### 4.2 Endpoint unificado (novo — Desk + Console) ```http GET /api/v1/dns/viewer/{domain} Authorization: Bearer Query: ?include_public=true&include_planned=true ``` **Response:** ```json { "domain": "empresa.com.br", "dns_mode": "ligbox_cf_ibytera", "mode_label": "Cloudflare Ligbox (ibytera)", "display_mode": "applied", "authoritative_source": "cf_ligbox", "nameservers": { "current_public": ["ns1.registro.br", "ns2.registro.br"], "ligbox_cloudflare": ["ada.ns.cloudflare.com", "bob.ns.cloudflare.com"], "match_ligbox": false }, "records": [ { "source": "cf_ligbox", "status": "applied", "type": "MX", "name": "empresa.com.br", "content": "mail.empresa.com.br", "priority": 10, "ttl": 3600, "purpose": "mx", "email_related": true } ], "planned_records": [], "public_checks": { "mx": { "ok": true, "values": ["10 mail.empresa.com.br"] }, "spf": { "ok": true }, "dkim": { "ok": false, "hint": "TXT _domainkey ausente" }, "dmarc": { "ok": true } }, "edit_links": [ { "label": "Editar na Cloudflare (Ligbox)", "provider": "cf_ligbox", "url": "https://dash.cloudflare.com/…/empresa.com.br/dns", "roles": ["super_admin", "ops_lead", "devops", "seo"] } ], "instructions": null, "errors": [] } ``` **Variante wizard (sessão cliente — sem staff JWT):** ```http GET /api/onboarding/dns/viewer/{domain} Header: X-Onboarding-Session: {session} ``` Reutiliza mesma shape; filtra `edit_links` vazios para cliente; mostra `planned_records` quando `display_mode=planned`. ### 4.3 Endpoints existentes reutilizados | Endpoint | Papel no viewer | |----------|-----------------| | `GET /api/onboarding/dns/resolve/{domain}` | Detectar `dns_mode` + conta CF | | `GET /api/onboarding/dns/instructions/{domain}` | Modo externo — o que colar | | `GET /api/onboarding/dns/verify/{domain}` | Checks públicos | | `GET /api/onboarding/dns/portal-onboarding/{domain}` | NS + passos registrador | | `GET /api/v1/dns/cloudflare/records` | **Legado Desk** — migrar para viewer | | Spec 009 audit | `public_checks` MX/SPF/DKIM/DMARC | ### 4.4 OpenPanel BIND (Fase 2 viewer) | Método | Descrição | |--------|-----------| | Desk proxy | `GET /api/v1/dns/openpanel/records?domain=` | | Backend | Bridge VM123 → OpenAdmin list records | | Fallback | SSH `docker exec openpanel_dns` zone dump (read-only) | Ver [028 DNS53](../028-openpanel-ce-ligbox-reengineering/DNS53_OPENPANEL_PORTA53.md). --- ## 5. Superfícies UI ### 5.1 Desk VM122 (staff) | Local | Componente | RBAC | |-------|------------|------| | Overview → modal tenant → clicar domínio | Secção **«DNS do domínio»** (substitui só-CF) | `cloudflare_dns.read` + roles 027 | | Serviços IaaS → modal domínio | Mesma secção embed | idem | | Chamado / ticket domínio | Tab DNS read-only | technician+ | **Wireframe Desk:** ``` ┌─ DNS — empresa.com.br ────────────────────────────────────────┐ │ [DNS Ligbox ▼] Zona activa · 14 registos · 6 e-mail │ │ NS públicos: ns1.registro.br … ⚠ ainda não apontam CF Ligbox │ ├───────────────────────────────────────────────────────────────┤ │ Função │ Nome │ Tipo │ Conteúdo │ Origem │ Estado │ │ MX │ @ │ MX │ mail… │ CF Lig │ ✅ aplicado │ │ SPF │ @ │ TXT │ v=spf1… │ CF Lig │ ✅ aplicado │ ├───────────────────────────────────────────────────────────────┤ │ Checks públicos (dig): MX ✅ SPF ✅ DKIM ⚠ DMARC ✅ │ ├───────────────────────────────────────────────────────────────┤ │ [Editar na Cloudflare ↗] [Verificação avançada] [Actualizar]│ └───────────────────────────────────────────────────────────────┘ ``` ### 5.2 Console gerente `/admin` (Spec 035) Secção **Domínio & DNS** — ver [domain-manager-console-ui.md §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md#55-domínio--dns-dns-viewer). Gerente vê read-only + link externo **se** tiver permissão (BYO: link CF cliente; Ligbox: mensagem «contacte suporte» ou link help). ### 5.3 Wizard passo DNS (cliente) | Estado wizard | Painel lateral viewer | |---------------|----------------------| | Escolheu Ligbox | Preview `planned_records` + NS | | Escolheu BYO | Registos actuais BYO + diff vs mail | | Escolheu Registrador | `instructions` + verify | --- ## 6. Links «Editar aqui» (deep-link) | Provider | Quem vê o botão | URL | Mecanismo | |----------|-----------------|-----|-----------| | Cloudflare Ligbox | staff `super_admin`, `ops_lead`, `devops`, `seo` | dash.cloudflare.com | Conta + zone_id de `dns/resolve` | | Cloudflare BYO | gerente (zona dele) + staff | dash.cloudflare.com | Zona BYO | | OpenPanel | staff + gerente hub | openpanel.ligbox.com.br | Autologin bridge 027 | | Registrador externo | gerente | URL detectada ou genérica | `instructions.registrar_url` | | Registro.br | gerente BR | https://registro.br | Manual | **Desk:** botão abre **nova tab** — nunca iframe CF (CSP). **Implementação Fase 1:** só link CF Ligbox (conta conhecida). Fase 2: OpenPanel autologin. --- ## 7. RBAC (Spec 027) | Acção | Roles | |-------|-------| | `dns.viewer.read` — ver painel | super_admin, ops_lead, technician, noc, seo, devops, developer | | Ver link Cloudflare Ligbox | super_admin, ops_lead, devops, seo | | Ver link OpenPanel | super_admin, ops_lead, sales_admin, sales_support, seo | | Gerente domínio `/admin` | Só domínio próprio; sem CF Ligbox interna | | Agente A2/A3 (Desk) | Lê viewer API — sugere fixes, não edita | Formalizar permissão `dns.viewer.read` em `data-model.md` 027 (backlog). --- ## 8. Persistência `dns_mode` Gravar em: | Store | Campo | |-------|-------| | Wizard sessão onboarding | `session.dns_mode`, `session.cf_account_id` | | `bundle_entitlements` (035) | `dns_mode`, `dns_provider`, `cf_zone_id` | | Desk `billing_accounts` / tenant meta | espelho read-only | Resolver ordem: 1. Entitlements domínio activo 2. Sessão onboarding em curso 3. `GET dns/resolve/{domain}` 4. NS lookup público → `unknown` --- ## 9. Fases de implementação | Fase | Entregável | Estado | |------|------------|--------| | **V0** | Desk CF only (`cloudflare_dns.py`) | ✅ Parcial | | **V1** | `GET /api/v1/dns/viewer/{domain}` — CF + public + planned | ✅ VM122 2026-06-25 | | **V1b** | Desk UI unificada (`dns-viewer.js`) | ✅ VM122 2026-06-25 | | **V2** | OpenPanel BIND records no viewer | ✅ VM122 2026-06-25 | | **V3** | Console `/admin/dominio` | ✅ VM123 2026-06-25 | | **V4** | Wizard painel lateral + diff planned vs actual | 📋 | --- ## 10. Critérios de aceitação 1. Staff abre domínio email Ligbox no Desk → vê **≥ MX, SPF, DKIM, DMARC, A mail** com origem «CF Ligbox». 2. Domínio **externo** (registrador) → viewer mostra **NS actuais** + registos **públicos** (`dig`), **não** preview Ligbox. 3. Domínio **Ligbox pré-NS** → viewer mostra **planned_records** + NS Cloudflare a configurar. 4. Botão «Editar na Cloudflare» visible para `ops_lead` → abre zona correcta (conta ibytera/ligit/itecnologys). 5. **Nenhum** botão «Guardar» / «Apagar registo» no viewer. 6. Gerente em `/admin` vê mesma tabela (domínio próprio) — Spec 035. 7. OpenPanel zone (Fase V2): registos BIND listados + link OpenPanel. 8. API responde ≤3s (cache 60s por domínio OK). --- ## 11. Código (alvo monorepo) | Caminho | Função | |---------|--------| | `projects/ops-desk/api/app/dns_viewer.py` | Orquestrador providers | | `projects/ops-desk/api/app/cloudflare_dns.py` | Provider CF (existente) | | `projects/ops-desk/api/app/openpanel_dns.py` | Provider BIND (novo) | | `projects/wizard/backend/app/services/dns_viewer.py` | Wizard session variant | | `projects/ops-desk/frontend/assets/dns-viewer.js` | Componente partilhado Desk | | `projects/console/frontend/.../DnsSection.tsx` | Console `/admin` | --- ## 12. Documentos relacionados | Doc | Relação | |-----|---------| | [spec.md](./spec.md) | Fluxo decisão DNS wizard | | [cloudflare-zone-provision.md](../004-onboard-funnel-events/cloudflare-zone-provision.md) | provision-zone / apply | | [domain-manager-console-ui.md §5.5](../035-ligbox-mail-bundles-foss-openpanel/domain-manager-console-ui.md) | UI gerente | | [009 spec](../009-ops-audit-overview/spec.md) | Checks públicos | | [027 spec](../027-desk-rbac-function-matrix/spec.md) | Deep-links CF / OP | | [DNS-VIEWER-ROLLBACK](./deploy/DNS-VIEWER-ROLLBACK.md) | Rollback e feature flag | --- ## 13. Decisões Roger (2026-06-25) | # | Decisão | |---|---------| | D1 | Viewer é **read-only** — edição só via link externo | | D2 | Caminho **Ligbox** → mostrar **planned/applied** Ligbox; **externo** → mostrar **actual** público | | D3 | OpenPanel BIND **deve** aparecer no viewer (Fase V2) — gap operacional actual | | D4 | Um endpoint unificado `dns/viewer` — não proliferar modais CF-only | | D5 | Mesmo componente visual Desk + Console `/admin` (design system 035) | --- ## 14. Versionamento e rollback **Obrigatório antes de cada deploy V1+:** backup + tag git + feature flag. | Documento | Função | |-----------|--------| | [deploy/DNS-VIEWER-EXEC-20260625.md](./deploy/DNS-VIEWER-EXEC-20260625.md) | Checklist deploy, ficheiros, validação | | [deploy/DNS-VIEWER-ROLLBACK.md](./deploy/DNS-VIEWER-ROLLBACK.md) | Rollback por fase (Desk / Wizard / Console) | | [deploy/scripts/preflight-dns-viewer.sh](./deploy/scripts/preflight-dns-viewer.sh) | Backup VM122 (+ `--wizard` / `--console`) | | [deploy/scripts/verify-dns-viewer.sh](./deploy/scripts/verify-dns-viewer.sh) | Smoke test pós-deploy ou pós-rollback | | [deploy/scripts/rollback-dns-viewer.sh](./deploy/scripts/rollback-dns-viewer.sh) | Rollback automatizado VM122 | ### Feature flag VM122 ```bash DNS_VIEWER_ENABLED=1 # viewer activo DNS_VIEWER_ENABLED=0 # fallback legado /dns/cloudflare/records + UI V0 ``` ### Tag git recomendada ```bash git tag -a dns-viewer-pre-v1-YYYYMMDD -m "Antes DNS Viewer V1" ``` ### Endpoint legado (mantido até cutover) `GET /api/v1/dns/cloudflare/records` — **não remover** enquanto rollback não estiver validado em produção ≥7 dias.