ligbox-ops-platform/specs/037-dns-multi-cloudflare-orchestration/dns-viewer.md
Ligbox Spec Hub 038fb8f7ce feat(dns): Spec 037 DNS Viewer — Desk, Console e patches Wizard V4
Entrega read-only de apontamentos DNS (Cloudflare, OpenPanel BIND, público)
no Desk e Console /admin/dominio, com spec, scripts de rollback e patches
VM112 para painel lateral no passo DNS do onboarding (deploy wizard pendente).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 16:36:25 +00:00

369 lines
16 KiB
Markdown

# 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 <JWT staff ou domain-admin>
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.