ligbox-ops-platform/specs/037-dns-multi-cloudflare-orchestration/dns-viewer.md
Ligbox Spec Hub 1f340ef924 docs(dns): sincronizar Spec 037/035 com deploy V1–V3 e V4 pendente
Actualiza dns-viewer, exec/rollback, fichas VM112/122/123, API Console
proxy, token interno wizard e verify script para evitar drift documental.

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

401 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec 037-DNS-VIEWER — Painel DNS read-only unificado
**Criado:** 2026-06-25
**Solicitado por:** Roger
**Status:** 🏗️ V1V3 em produção (VM122/VM123) · V4 patches prontos · deploy VM112 pendente
**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>
# OU (wizard / workers internos):
X-Ops-Internal-Token: <OPS_INTERNAL_TOKEN>
Query: ?include_public=true&email_service=true
```
**Variante Console gerente (Spec 035 V3):**
```http
GET /api/v1/domain-console/dns/viewer/{domain}
Authorization: Bearer <JWT domain-admin ou staff>
Query: ?email_service=true&include_public=true
```
Implementação: `domain_console_routes.py` — proxy para `fetch_dns_viewer()`; filtra `edit_links` CF Ligbox para roles não-staff.
**Autenticação interna (V4 wizard → Desk):**
| Header | Quem usa | Função |
|--------|----------|--------|
| `Authorization: Bearer …` | Desk UI, Console `/admin` | JWT staff ou domain-admin |
| `X-Ops-Internal-Token` | VM112 wizard backend | `require_internal_or_user` em `main.py` — evita duplicar lógica DNS no wizard |
Variáveis VM112: `DESK_API_URL=http://10.10.10.122:8080`, `OPS_INTERNAL_TOKEN` (mesmo valor Desk).
**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 | 📋 patches no repo · deploy VM112 pendente (SSH) |
---
## 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 (monorepo + deploy)
| Caminho | Função | Deploy |
|---------|--------|--------|
| `projects/ops-desk/api/app/dns_viewer.py` | Orquestrador providers | VM122 API |
| `projects/ops-desk/api/app/cloudflare_dns.py` | Provider CF | VM122 API |
| `projects/ops-desk/api/app/openpanel_dns.py` | Provider BIND (`dig @10.10.10.123`) | VM122 API |
| `projects/ops-desk/api/app/domain_console_routes.py` | Proxy Console `/admin/dominio` | VM122 API |
| `projects/ops-desk/api/app/main.py` | Rotas viewer + `require_internal_or_user` | VM122 API |
| `projects/ops-desk/frontend/assets/dns-viewer.js` | Componente Desk | VM122 frontend |
| `specs/019-.../deploy/frontend/src/components/DnsViewerPanel.jsx` | Componente Console | VM123 |
| `specs/019-.../deploy/frontend/src/views/admin/AdminDominio.jsx` | Página `/admin/dominio` | VM123 |
| `deploy/vm112-wizard/onboarding-dns-viewer-v4.patch.py` | Endpoint wizard proxy Desk | VM112 backend |
| `deploy/vm112-wizard/frontend-dns-viewer-v4.patch.py` | Painel lateral step DNS | VM112 frontend |
| `deploy/vm112-wizard/DNS-VIEWER-V4.md` | Runbook deploy V4 | — |
**Nota deploy VM122:** código API corre em container Docker — após `rsync` ao host, copiar para o container (`docker cp …/main.py …_api_1:/app/app/`) ou rebuild imagem.
---
## 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 |
| [DNS-VIEWER-EXEC-20260625](./deploy/DNS-VIEWER-EXEC-20260625.md) | Registo deploys e commits |
| [deploy/vm112-wizard/DNS-VIEWER-V4.md](../../../deploy/vm112-wizard/DNS-VIEWER-V4.md) | Runbook V4 wizard |
---
## 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"
```
Entrega monorepo V1V3 + patches V4: commit **`038fb8f`** (2026-06-25).
### 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.