ligbox-ops-platform/specs/037-dns-multi-cloudflare-orchestration/dns-viewer.md
Ligbox Spec Hub c1881f58e6 chore: sync Console SSO, DNS viewer, specs e infra docs pendentes
Inclui console handoff Desk↔Console (Spec 019), melhorias DNS Viewer (037),
OpenPanel/Nextcloud/VM116 deploy notes, contracts stack e sidebar actualizado.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 20:10:17 +00:00

477 lines
22 KiB
Markdown
Raw Permalink 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 | Staff: `https://admin.openpanel.ligbox.com.br/domains/dns?domain={domain}` · Cliente: `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)
### Regra Roger (2026-06-25) — staff vs cliente
| Princípio | Detalhe |
|-----------|---------|
| **Visualização DNS** | Read-only na Console/Desk — **perfeita para suporte** (apontamentos, faltas, erros, origem) |
| **Login Console `/admin/*`** | **Uma vez** no shell Admin (`AdminShell`) — **não** por página. Handoff Desk→Console (`POST /auth/console-handoff` + `?desk_handoff=`) evita re-login ao navegar setups do Wizard |
| **Desk → Console** | Botão **«Abrir no Console ↗»** no painel DNS (staff com `can_read_dns_viewer`) |
| **Suporte NUNCA** | Painel **cliente** OpenPanel (`openpanel.ligbox.com.br/domains/…/dns`) — dá 500 e é vista errada |
| **Suporte SEMPRE** | **OpenAdmin** (`admin.openpanel.ligbox.com.br/domains/dns?domain=…`) ou Cloudflare **conta partner Ligbox** |
| **Gerente domínio** (futuro) | Links `audience: client` — painel cliente / CF BYO própria; **sem** OpenAdmin |
Campo `audience` em cada link: `staff` | `client` | `all`.
| Provider | Quem vê | URL staff | URL cliente |
|----------|---------|-----------|-------------|
| Cloudflare Ligbox | staff | `dash.cloudflare.com/.../zones/{zone_id}/dns` | — (gerente: «contacte suporte») |
| Cloudflare BYO | gerente + staff | dash CF zona BYO | idem |
| OpenPanel BIND | staff | `https://admin.openpanel.ligbox.com.br/domains/dns?domain={domain}` | `https://openpanel.ligbox.com.br/domains/{domain}/dns` |
| Registrador | todos | registro.br / detectado | idem |
**Desk / Console:** botão abre **nova tab** — nunca iframe.
**Implementação:** `dns_viewer._edit_links()` + `permissions.can_open_openpanel_admin_link()` vs `can_open_openpanel_client_link()`.
---
## 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 OpenAdmin (staff) | super_admin, ops_lead, devops, sales_admin, sales_support, seo, technician, noc |
| Ver link OpenPanel cliente | domain_manager (futuro) — **proibido** para staff suporte |
| 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).
---
## Incidente 2026-06-25 — Console «Failed to fetch» no login DNS
### Sintoma
`console.ligbox.com.br/admin/dominio` → Login Desk → **Failed to fetch** (abaixo do botão Entrar).
«Sem dados DNS.» aparece em baixo — **não é erro**, é estado vazio antes de login + consulta.
### Causa
O frontend Console (`api.js`) enviava `credentials: 'include'` nas chamadas cross-origin para `https://api.ops.ligbox.com.br`.
A API Desk responde `Access-Control-Allow-Origin: *`. Browsers **bloqueiam** pedidos credenciados com wildcard → `TypeError: Failed to fetch`.
### Correção
1. Remover `credentials: 'include'` do `apiGet` / `apiPost` — auth usa **Bearer JWT** em `localStorage`, não cookies.
2. API Desk: `CORS_ORIGINS` explícito inclui `https://console.ligbox.com.br` (deixa de depender só de `*`).
3. Console nginx: `index.html` com `Cache-Control: no-cache` para forçar bundle novo após deploy.
4. Mensagens de erro amigáveis quando a rede/CORS falha; sessão expirada (401) pede re-login.
| Ficheiro | Alteração |
|----------|-----------|
| `specs/019-.../deploy/frontend/src/lib/api.js` | Sem `credentials`; `wrapNetworkError` |
| `specs/019-.../deploy/frontend/src/views/admin/AdminDominio.jsx` | 401 → clear session |
| `projects/ops-desk/api/app/main.py` | `CORS_ORIGINS` |
| `specs/019-.../deploy/nginx/default.conf` | no-cache em `index.html` |
### Consulta DNS (ex.: diarissima.com)
Após fix, `GET /api/v1/domain-console/dns/viewer/diarissima.com` retorna **200** (~3s) com registos OpenPanel BIND — validado 2026-06-25.
---
## Incidente 2026-06-25 — Link «Editar no OpenPanel» abria painel cliente (500)
### Sintoma
Staff em Console → `diarissima.com`**Editar no OpenPanel**`openpanel.ligbox.com.br/domains/diarissima.com/dns`**ERROR 500** (vista **cliente**, não OpenAdmin).
### Causa
`edit_links` apontava `OPENPANEL_URL` (painel utilizador) em vez de `OPENADMIN_URL` (DNS Zone Editor staff).
### Correção
| Antes | Depois (staff) |
|-------|----------------|
| `openpanel.ligbox.com.br/domains/{d}/dns` | `admin.openpanel.ligbox.com.br/domains/dns?domain={d}` |
| Label «Editar no OpenPanel» | «Editar DNS (OpenAdmin)» |
| `provider: openpanel_bind` | `provider: openpanel_admin`, `audience: staff` |
Gerente futuro (`domain_manager`) receberá link cliente separado — staff **nunca**.
### Ficheiros
`openpanel_dns.py`, `dns_viewer.py`, `permissions.py`, `domain_console_routes.py`, `dns-viewer.md` §6.
### Credenciais
- **Login Desk** exige utilizador **Ops Desk** (ex.: `Roger`, `root`) — **não** `ligboxadmin` (conta OpenAdmin/hosting).
- Após login: introduzir domínio e **Consultar DNS**.
### 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.