From 68ec7bc9017d4d4480750de1a42581bc16b2d217 Mon Sep 17 00:00:00 2001 From: Ligbox Spec Hub Date: Fri, 19 Jun 2026 22:33:29 +0000 Subject: [PATCH] Standardize Infra process cards with uniform grid and detail modals. Adopt proc-card pattern (fixed min-height, icons, spec labels, 2-line desc) per UX research; move purge forms and rich content to infra-process-modal; document catalog in Spec 033. Co-authored-by: Cursor --- projects/ops-desk/frontend/assets/app.js | 380 +++++++++++++------ projects/ops-desk/frontend/assets/styles.css | 96 +++++ projects/ops-desk/frontend/index.html | 29 +- specs/033-desk-infra-console-ui/spec.md | 141 +++---- 4 files changed, 463 insertions(+), 183 deletions(-) diff --git a/projects/ops-desk/frontend/assets/app.js b/projects/ops-desk/frontend/assets/app.js index 313aec7..08617d1 100644 --- a/projects/ops-desk/frontend/assets/app.js +++ b/projects/ops-desk/frontend/assets/app.js @@ -3825,17 +3825,190 @@ function infraKvHtml(items) { ).join('')}`; } +const PROC_CARD_ICONS = { + soc: '📡', + openpanel: '🎛️', + purge: '🔐', + vm112: '🌐', + wazuh: '🛡️', + integrations: '🔗', +}; + +function procCardHtml(opts) { + const { + id, + icon, + accent = 'teal', + title, + spec, + desc, + statusLabel, + statusCls = 'review', + actions = [], + } = opts; + const acts = actions.map((a) => + `` + ).join(''); + return ` +
+ ${esc(statusLabel)} +
+ + ${esc(spec)} +
+

${esc(title)}

+

${desc}

+
${acts}
+
`; +} + +function closeInfraProcessModal() { + const modal = document.getElementById('infra-process-modal'); + if (!modal) return; + modal.classList.add('hidden'); + modal.setAttribute('aria-hidden', 'true'); +} + +function openInfraProcessModal(title, sub, bodyHtml) { + const modal = document.getElementById('infra-process-modal'); + const titleEl = document.getElementById('infra-process-modal-title'); + const subEl = document.getElementById('infra-process-modal-sub'); + const body = document.getElementById('infra-process-modal-body'); + if (!modal || !body) return; + if (titleEl) titleEl.textContent = title; + if (subEl) subEl.textContent = sub || ''; + body.innerHTML = bodyHtml; + modal.classList.remove('hidden'); + modal.setAttribute('aria-hidden', 'false'); +} + +function bindInfraProcessModal() { + document.querySelectorAll('[data-close-infra-process-modal]').forEach((el) => { + el.addEventListener('click', closeInfraProcessModal); + }); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') closeInfraProcessModal(); + }); +} + +function openInfraProcessDetail(procId) { + const snap = state.infraSnapshot; + if (!snap) return; + const { vm112, wazuh, integrations, health, vm123Health, purgeMeta } = snap; + const onboard = health.vm112_onboard || {}; + const last = onboard.last_webhook; + const gap = onboard.gap_minutes != null ? `${Math.round(onboard.gap_minutes)} min` : '—'; + const vmOk = onboard.vm112_api?.reachable; + const wazuhOk = wazuh.http_status === 200; + const op = vm123Health?.openpanel || {}; + const opOk = Boolean(op.ok); + const bridgeOk = Boolean(op.bridge); + const alerts = (health.alerts || []).map((a) => + `
  • ${esc(a.message)}
  • ` + ).join('') || '
  • Nenhum alerta activo
  • '; + + if (procId === 'soc') { + openInfraProcessModal( + 'SOC — Integração VM112', + 'Webhook onboard · alertas de gap', + `${infraKvHtml([ + ['Último evento', last ? esc(last.event) : '—'], + ['Domínio', last?.domain ? esc(last.domain) : '—'], + ['Há quanto tempo', gap], + ['VM112 API', vmOk ? 'OK' : esc(onboard.vm112_api?.error || 'offline')], + ['Status integração', esc(health.status || '—')], + ])} + +
    + + +
    +

    Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.

    ` + ); + document.getElementById('btn-test-webhook-modal')?.addEventListener('click', () => runWebhookIntegrationTest('infra')); + document.getElementById('btn-refresh-health-modal')?.addEventListener('click', () => { + closeInfraProcessModal(); + renderInfra(); + }); + return; + } + if (procId === 'openpanel') { + openInfraProcessModal( + 'OpenPanel API — Re-engenharia Ligbox', + 'Spec 028 · VM123 bridge :18087', + `

    Multidomínio · conta temporária com cleanup automático.

    + ${vm123Health + ? infraKvHtml([ + ['OpenPanel', opOk ? 'OK' : esc(op.error || 'offline')], + ['Bridge API', bridgeOk ? 'OK' : 'offline'], + ['Bridge URL', esc(op.bridge_url || '—')], + ['VM123', vm123Health.ok ? 'OK' : esc(vm123Health.error || 'check')], + ]) + : '

    Status VM123 indisponível.

    '} +
    + + OpenPanel UI +
    +

    Suite openpanel-multidomain-api-confirm

    ` + ); + document.getElementById('btn-test-openpanel-modal')?.addEventListener('click', () => runOpenPanelApiTest()); + return; + } + if (procId === 'purge') { + openInfraProcessModal( + 'Códigos purge — autorização extra', + 'Spec 032 · domínios protegidos', + '

    A carregar…

    ' + ); + renderPurgeAuthPanel(document.getElementById('purge-auth-modal-panel')); + return; + } + if (procId === 'vm112') { + openInfraProcessModal( + 'VM112 — Onboard Portal', + 'HTTP health · serviço portal', + infraKvHtml([ + ['HTTP', String(vm112.http_status ?? '—')], + ['Service', esc(vm112.vm112?.service || vm112.error || '—')], + ['API integração', vmOk ? 'OK' : 'offline'], + ]) + ); + return; + } + if (procId === 'wazuh') { + openInfraProcessModal( + 'VM104 — Wazuh SOC', + 'Spec 002 · API + webhook', + infraKvHtml([ + ['API HTTP', String(wazuh.http_status ?? '—')], + ['Integração', 'webhook level ≥ 10 → VM122'], + ['Status', wazuhOk ? 'online' : 'check'], + ]) + ); + return; + } + if (procId === 'integrations') { + openInfraProcessModal( + 'Integrações activas', + 'Snapshot JSON · Desk API', + `
    ${esc(JSON.stringify(integrations, null, 2))}
    ` + ); + } +} + async function renderInfra() { const el = document.getElementById('infra-content'); el.innerHTML = '

    Verificando…

    '; try { - const [vm112, wazuh, integrations, health, vm123Health] = await Promise.all([ + const [vm112, wazuh, integrations, health, vm123Health, purgeMeta] = await Promise.all([ api('/v1/infra/vm112/status'), api('/v1/infra/wazuh/status'), api('/v1/integrations'), api('/v1/integrations/health'), api('/v1/vm123/health').catch(() => null), + api('/v1/infra/purge-auth-domains').catch(() => ({ domains: [], can_generate: false })), ]); + state.infraSnapshot = { vm112, wazuh, integrations, health, vm123Health, purgeMeta }; const onboard = health.vm112_onboard || {}; const last = onboard.last_webhook; const gap = onboard.gap_minutes != null ? `${Math.round(onboard.gap_minutes)} min` : '—'; @@ -3845,124 +4018,110 @@ async function renderInfra() { const opOk = Boolean(op.ok); const bridgeOk = Boolean(op.bridge); const statusCls = health.status === 'ok' ? 'ok' : health.status === 'critical' ? 'escalated' : 'assisting'; - const heroHealthDot = health.status === 'ok' ? '' : health.status === 'critical' ? 'infra-hero-dot--bad' : 'infra-hero-dot--warn'; - const alerts = (health.alerts || []).map((a) => - `
  • ${esc(a.message)}
  • ` - ).join('') || '
  • Nenhum alerta activo
  • '; + const purgeDomains = purgeMeta.domains || []; + const purgeLabel = purgeDomains.length ? `${purgeDomains.length} domínio(s)` : 'sem extra-auth'; el.innerHTML = `
    -
    -
    - -
    - SOC integração - Webhook VM112 · gap ${gap} -
    - ${esc(health.status || '—')} -
    -
    - -
    - VM112 Portal - ${esc(vm112.vm112?.service || vm112.error || '—')} -
    - ${vmOk ? 'online' : 'check'} -
    -
    - -
    - VM104 Wazuh - API HTTP ${wazuh.http_status ?? '—'} -
    - ${wazuhOk ? 'online' : 'check'} -
    -
    - -
    - OpenPanel VM123 - Bridge ${bridgeOk ? 'OK' : 'check'} · ${esc(op.bridge_url || '10.10.10.123:18087')} -
    - ${opOk ? 'online' : 'check'} -
    -
    -
    -
    -
    SOC — Integração VM112
    -
    - ${infraKvHtml([ - ['Último evento', last ? esc(last.event) : '—'], - ['Domínio', last?.domain ? esc(last.domain) : '—'], - ['Há quanto tempo', gap], - ['VM112 API', vmOk ? 'OK' : esc(onboard.vm112_api?.error || 'offline')], - ])} -
      ${alerts}
    -
    - - -
    -

    Alerta se gap > ${health.webhook_gap_alert_minutes || 15} min sem eventos VM112.

    -
    -
    - -
    -
    Códigos purge · Spec 032
    -

    A carregar…

    -
    -
    -
    VM112 — Onboard
    -
    - ${infraKvHtml([ - ['HTTP', String(vm112.http_status ?? '—')], - ['Service', esc(vm112.vm112?.service || vm112.error || '—')], - ])} -
    -
    -
    -
    VM104 — Wazuh SOC
    -
    - ${infraKvHtml([ - ['API HTTP', String(wazuh.http_status ?? '—')], - ['Integração', 'webhook level ≥ 10 → VM122'], - ])} -
    -
    -
    -
    Integrações activas
    -
    -
    ${esc(JSON.stringify(integrations, null, 2))}
    -
    -
    +

    Processos de infraestrutura · cards uniformes · detalhes e formulários em modal (Spec 033).

    +
    + ${procCardHtml({ + id: 'soc', + icon: PROC_CARD_ICONS.soc, + accent: 'teal', + title: 'SOC VM112', + spec: 'Webhook', + desc: `Gap ${gap} · ${last?.event ? esc(last.event) : 'sem eventos recentes'}`, + statusLabel: health.status || '—', + statusCls, + actions: [ + { id: 'btn-proc-soc-detail', label: 'Detalhes', primary: true }, + { id: 'btn-test-webhook', label: 'Testar', primary: false }, + ], + })} + ${procCardHtml({ + id: 'openpanel', + icon: PROC_CARD_ICONS.openpanel, + accent: 'orange', + title: 'OpenPanel API', + spec: 'Spec 028', + desc: `Bridge ${bridgeOk ? 'OK' : 'check'} · ${esc(op.bridge_url || '10.10.10.123:18087')}`, + statusLabel: opOk ? 'online' : 'check', + statusCls: opOk ? 'ok' : 'review', + actions: [ + { id: 'btn-proc-openpanel-detail', label: 'Detalhes', primary: true }, + { id: 'btn-test-openpanel-api', label: 'Testar', primary: false }, + ], + })} + ${procCardHtml({ + id: 'purge', + icon: PROC_CARD_ICONS.purge, + accent: 'rose', + title: 'Códigos purge', + spec: 'Spec 032', + desc: purgeDomains.length + ? `Protegidos: ${purgeDomains.map((d) => esc(d)).join(', ')}` + : 'Geração de códigos para domínios com autorização extra', + statusLabel: purgeLabel, + statusCls: purgeDomains.length ? 'assisting' : 'open', + actions: [ + { id: 'btn-proc-purge-manage', label: 'Gerir códigos', primary: true }, + ], + })} + ${procCardHtml({ + id: 'vm112', + icon: PROC_CARD_ICONS.vm112, + accent: 'aqua', + title: 'VM112 Onboard', + spec: 'Portal', + desc: esc(vm112.vm112?.service || vm112.error || 'Portal de onboarding'), + statusLabel: vmOk ? 'online' : 'check', + statusCls: vmOk ? 'ok' : 'review', + actions: [ + { id: 'btn-proc-vm112-detail', label: 'Ver status', primary: true }, + ], + })} + ${procCardHtml({ + id: 'wazuh', + icon: PROC_CARD_ICONS.wazuh, + accent: 'slate', + title: 'Wazuh SOC', + spec: 'Spec 002', + desc: `API HTTP ${wazuh.http_status ?? '—'} · alertas nível ≥ 10`, + statusLabel: wazuhOk ? 'online' : 'check', + statusCls: wazuhOk ? 'ok' : 'review', + actions: [ + { id: 'btn-proc-wazuh-detail', label: 'Ver status', primary: true }, + ], + })} + ${procCardHtml({ + id: 'integrations', + icon: PROC_CARD_ICONS.integrations, + accent: 'violet', + title: 'Integrações', + spec: 'JSON', + desc: 'Snapshot das integrações configuradas no Desk', + statusLabel: 'activas', + statusCls: 'open', + actions: [ + { id: 'btn-proc-integrations-json', label: 'Ver JSON', primary: true }, + ], + })}
    `; - document.getElementById('btn-refresh-health')?.addEventListener('click', () => renderInfra()); document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra')); document.getElementById('btn-test-openpanel-api')?.addEventListener('click', () => runOpenPanelApiTest()); - await renderPurgeAuthInfraPanel(); + document.getElementById('btn-proc-soc-detail')?.addEventListener('click', () => openInfraProcessDetail('soc')); + document.getElementById('btn-proc-openpanel-detail')?.addEventListener('click', () => openInfraProcessDetail('openpanel')); + document.getElementById('btn-proc-purge-manage')?.addEventListener('click', () => openInfraProcessDetail('purge')); + document.getElementById('btn-proc-vm112-detail')?.addEventListener('click', () => openInfraProcessDetail('vm112')); + document.getElementById('btn-proc-wazuh-detail')?.addEventListener('click', () => openInfraProcessDetail('wazuh')); + document.getElementById('btn-proc-integrations-json')?.addEventListener('click', () => openInfraProcessDetail('integrations')); } catch (e) { el.innerHTML = `

    Erro: ${esc(e.message)}

    `; } } -async function renderPurgeAuthInfraPanel() { - const panel = document.getElementById('purge-auth-infra-panel'); +async function renderPurgeAuthPanel(panel) { if (!panel) return; try { const meta = await api('/v1/infra/purge-auth-domains'); @@ -4060,6 +4219,10 @@ async function renderPurgeAuthInfraPanel() { } } +async function renderPurgeAuthInfraPanel() { + await renderPurgeAuthPanel(document.getElementById('purge-auth-infra-panel')); +} + async function refresh(options = {}) { const { poll = false } = options; await loadHealth(); @@ -4143,6 +4306,7 @@ document.getElementById('btn-refresh')?.addEventListener('click', () => { applyRoleNav(); DeskModules.applyVisibility(); bindOverviewModal(); + bindInfraProcessModal(); bindTeamDrawerClose(); bindSocTestModal(); setView('dashboard'); diff --git a/projects/ops-desk/frontend/assets/styles.css b/projects/ops-desk/frontend/assets/styles.css index 70f9bf3..733c50f 100644 --- a/projects/ops-desk/frontend/assets/styles.css +++ b/projects/ops-desk/frontend/assets/styles.css @@ -4111,6 +4111,102 @@ button.health-card { background: #0f172a; } +/* Process cards — grid uniforme (Spec 033 § proc-card) */ +.proc-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 16px; + align-items: stretch; +} +.proc-card { + position: relative; + display: flex; + flex-direction: column; + min-height: 168px; + padding: 16px; + border-radius: 12px; + border: 1px solid #e2e8f0; + background: #fff; + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); +} +.proc-card--teal { border-top: 3px solid #14b8a6; } +.proc-card--orange { border-top: 3px solid #f97316; } +.proc-card--rose { border-top: 3px solid #f43f5e; } +.proc-card--slate { border-top: 3px solid #64748b; } +.proc-card--violet { border-top: 3px solid #8b5cf6; } +.proc-card--aqua { border-top: 3px solid #06b6d4; } +.proc-card-head { + display: flex; + align-items: flex-start; + gap: 8px; + min-height: 32px; +} +.proc-card-icon { + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + font-size: 1.05rem; + line-height: 1; + flex-shrink: 0; +} +.proc-card--teal .proc-card-icon { background: #ccfbf1; } +.proc-card--orange .proc-card-icon { background: #ffedd5; } +.proc-card--rose .proc-card-icon { background: #ffe4e6; } +.proc-card--slate .proc-card-icon { background: #f1f5f9; } +.proc-card--violet .proc-card-icon { background: #ede9fe; } +.proc-card--aqua .proc-card-icon { background: #cffafe; } +.proc-card-spec { + font-size: 0.62rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #64748b; + margin-left: auto; + text-align: right; + line-height: 1.2; + max-width: 42%; +} +.proc-card-badge { + position: absolute; + top: 12px; + right: 12px; + font-size: 0.62rem; +} +.proc-card-title { + margin: 8px 0 0; + font-size: 0.88rem; + font-weight: 600; + color: #0f172a; + line-height: 1.35; + padding-right: 3.5rem; +} +.proc-card-desc { + margin: 6px 0 0; + font-size: 0.75rem; + color: #64748b; + line-height: 1.45; + flex: 1; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.proc-card-foot { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #f1f5f9; +} +.proc-card-foot .btn { flex: 1 1 auto; min-width: 0; } +@media (max-width: 480px) { + .proc-grid { grid-template-columns: 1fr; } +} + /* Spec 021 — Acesso utilizador (separado do VM112 Onboard) */ .ws-access-zone { margin-bottom: 1.25rem; diff --git a/projects/ops-desk/frontend/index.html b/projects/ops-desk/frontend/index.html index ccb3e0b..67ec74d 100644 --- a/projects/ops-desk/frontend/index.html +++ b/projects/ops-desk/frontend/index.html @@ -413,14 +413,27 @@
    + - - - - - - - - + + + + + + + + diff --git a/specs/033-desk-infra-console-ui/spec.md b/specs/033-desk-infra-console-ui/spec.md index 5246a6f..cad45a6 100644 --- a/specs/033-desk-infra-console-ui/spec.md +++ b/specs/033-desk-infra-console-ui/spec.md @@ -1,6 +1,7 @@ -# Spec 033 — Desk Infra Console UI (ws-panel aqua/teal) +# Spec 033 — Desk Infra Console UI (process cards) **Criado:** 2026-06-19 +**Actualizado:** 2026-06-19 (padronização `proc-card`) **Solicitado por:** Roger **Prioridade:** P2 (UX operacional) **Status:** ✅ Implementado (Desk VM122 frontend) @@ -11,9 +12,30 @@ ## Resumo -Redesenho da página **Infraestrutura** do Desk: layout tipo wizard (`ws-panel` + gradientes teal/aqua/orange), chips de status no topo e painéis por integração — substituindo cards genéricos pouco legíveis. +Página **Infraestrutura** do Desk com **process cards** uniformes (`proc-card`): mesmo tamanho, tipografia, ícone, badge de status e acções no rodapé. Conteúdo rico (métricas, formulários, JSON) abre em **modal largo** (`#infra-process-modal`). -**Motivo:** cards antigos (`class="card"`) eram visualmente pobres e o painel OpenPanel (Spec 028) ficou pouco visível após o primeiro redesign. +**Motivo:** painéis `ws-panel` de alturas variadas quebravam o alinhamento visual; inputs inline (purge) inflavam cards. + +--- + +## Referência de design (pesquisa) + +Padrões adoptados de boas práticas de UI card (Material UI equal-height, UX Collective, CSS Grid auto-fill): + +| Regra | Valor Desk | +|-------|------------| +| Grid | `repeat(auto-fill, minmax(220px, 1fr))` · gap **16px** (sistema 8pt) | +| Altura mínima card | **168px** · `flex-column` + `justify` implícito via footer | +| Padding card | **16px** | +| Título | **0.88rem** · weight 600 | +| Spec label | **0.62rem** uppercase · letter-spacing 0.05em | +| Descrição | **0.75rem** · **2 linhas** (`line-clamp: 2`) | +| Ícone | **32×32px** · fundo pastel por accent | +| Badge status | canto superior direito · classes `badge` existentes | +| Inputs / tabelas / JSON | **modal** `modal-panel-lg` — nunca no card | +| Testes (webhook, OpenPanel) | botão rápido no card **ou** no modal · resultado em `#soc-test-modal` | + +**Alinhamento igual altura:** grid `align-items: stretch` + `min-height` fixo no card (não aspect-ratio — conteúdo operacional, não media). --- @@ -27,71 +49,60 @@ Redesenho da página **Infraestrutura** do Desk: layout tipo wizard (`ws-panel` --- -## Layout +## Catálogo de process cards (Infra) -### 1. Hero — chips de status (`infra-hero`) +| ID | Ícone | Título | Spec label | Accent | Status | Acções card | +|----|-------|--------|------------|--------|--------|-------------| +| `soc` | 📡 | SOC VM112 | Webhook | teal | `health.status` | Detalhes · Testar | +| `openpanel` | 🎛️ | OpenPanel API | Spec 028 | orange | online/check | Detalhes · Testar | +| `purge` | 🔐 | Códigos purge | Spec 032 | rose | N domínios | Gerir códigos → modal | +| `vm112` | 🌐 | VM112 Onboard | Portal | aqua | online/check | Ver status | +| `wazuh` | 🛡️ | Wazuh SOC | Spec 002 | slate | online/check | Ver status | +| `integrations` | 🔗 | Integrações | JSON | violet | activas | Ver JSON | -Quatro chips com dot + badge: +Mapa de ícones: constante `PROC_CARD_ICONS` em `app.js`. -| Chip | Fonte API | -|------|-----------| -| SOC integração | `GET /api/v1/integrations/health` | -| VM112 Portal | idem + `GET /api/v1/infra/vm112/status` | -| VM104 Wazuh | `GET /api/v1/infra/wazuh/status` | -| **OpenPanel VM123** | `GET /api/v1/vm123/health` → `openpanel` | +--- -### 2. Grid — painéis `ws-panel` (`infra-grid`) +## Modais -| Painel | Cabeçalho CSS | Largura | Spec / função | -|--------|---------------|---------|----------------| -| SOC VM112 | `ws-panel-head--teal` | wide | Webhook + alertas | -| **OpenPanel API** | `ws-panel-head--orange` | **wide + featured** | **028** — teste multidomínio | -| Códigos purge | `ws-panel-head--rose` | normal | **032** — geração códigos | -| VM112 Onboard | teal | normal | HTTP / service | -| VM104 Wazuh | `ws-panel-head--slate` | normal | API SOC | -| Integrações JSON | `ws-panel-head--violet` | wide | dump `/integrations` | +| Modal | Uso | +|-------|-----| +| `#infra-process-modal` | Detalhes, formulário purge, JSON integrações | +| `#soc-test-modal` | Resultado testes webhook / OpenPanel multidomínio | -### 3. OpenPanel (Spec 028) — conteúdo obrigatório +Funções: `openInfraProcessModal()`, `openInfraProcessDetail(procId)`, `closeInfraProcessModal()`, `bindInfraProcessModal()`. -Painel **largura total**, classe `infra-panel--featured`: - -- Título: `OpenPanel API — Re-engenharia Ligbox · Spec 028` -- Métricas: OpenPanel OK, Bridge API, Bridge URL, VM123 health -- Botões: **Testar multidomínio** (`POST /api/v1/vm123/openpanel/test-confirm`) · link OpenPanel UI -- Hint: suite `openpanel-multidomain-api-confirm` + script CLI - -Modal de resultado: reutiliza `#soc-test-modal` (`showOpenPanelTestResult`). - -### 4. Purge auth (Spec 032) - -Sub-render: `renderPurgeAuthInfraPanel()` → `#purge-auth-infra-panel` +Snapshot API: `state.infraSnapshot` (reutilizado ao abrir detalhe sem re-fetch). --- ## APIs consumidas -| Endpoint | Uso na página | -|----------|----------------| -| `GET /api/v1/infra/vm112/status` | Chip + painel VM112 | -| `GET /api/v1/infra/wazuh/status` | Chip + painel Wazuh | -| `GET /api/v1/integrations` | JSON painel violet | -| `GET /api/v1/integrations/health` | Chip SOC + alertas | -| `GET /api/v1/vm123/health` | Chip + painel OpenPanel | -| `GET /api/v1/infra/purge-auth-domains` | Spec 032 panel | -| `GET/POST /api/v1/infra/purge-auth-codes` | Spec 032 panel | -| `POST /api/v1/vm123/openpanel/test-confirm` | Botão teste OpenPanel | +| Endpoint | Uso | +|----------|-----| +| `GET /api/v1/infra/vm112/status` | Card VM112 + modal | +| `GET /api/v1/infra/wazuh/status` | Card Wazuh + modal | +| `GET /api/v1/integrations` | Card + modal JSON | +| `GET /api/v1/integrations/health` | Card SOC + modal | +| `GET /api/v1/vm123/health` | Card OpenPanel + modal | +| `GET /api/v1/infra/purge-auth-domains` | Card purge + modal | +| `GET/POST /api/v1/infra/purge-auth-codes` | Modal purge (Spec 032) | +| `POST /api/v1/vm123/openpanel/test-confirm` | Teste OpenPanel | --- ## CSS (`frontend/assets/styles.css`) -Classes principais: +Classes **proc-card** (padrão reutilizável em outras páginas): -- `.infra-page`, `.infra-hero`, `.infra-hero-chip`, `.infra-hero-dot` -- `.infra-grid`, `.infra-panel`, `.infra-panel--wide`, `.infra-panel--featured` -- `.infra-kv` — métricas em grid -- `.infra-actions`, `.infra-hint`, `.infra-alert-list` -- Reutiliza `.ws-panel`, `.ws-panel-head--{teal|orange|rose|slate|violet}` (wizard/SOC) +- `.proc-grid`, `.proc-card`, `.proc-card--{teal|orange|rose|slate|violet|aqua}` +- `.proc-card-head`, `.proc-card-icon`, `.proc-card-spec`, `.proc-card-badge` +- `.proc-card-title`, `.proc-card-desc`, `.proc-card-foot` + +Helpers Infra (modais): `.infra-kv`, `.infra-actions`, `.infra-hint`, `.purge-auth-form` + +Helper JS: `procCardHtml(opts)` — gera HTML uniforme. --- @@ -99,36 +110,32 @@ Classes principais: | Ficheiro | Função | |----------|--------| -| `frontend/assets/app.js` | `renderInfra()`, `renderPurgeAuthInfraPanel()`, `infraKvHtml()` | -| `frontend/assets/styles.css` | Estilos Infra + purge auth form | -| `frontend/index.html` | `#infra-content` em `#view-infra` | - -**Commits:** `41c0c2d` (layout inicial) · follow-up OpenPanel featured panel. +| `frontend/assets/app.js` | `renderInfra()`, `procCardHtml()`, modais Infra | +| `frontend/assets/styles.css` | `.proc-card` + Infra | +| `frontend/index.html` | `#infra-content`, `#infra-process-modal` | --- ## Critérios de aceitação -1. Infra mostra **4 chips** no topo (incl. OpenPanel VM123). -2. Painel OpenPanel é **largura total**, título Spec 028, métricas bridge e botão teste. -3. Botão «Testar multidomínio» abre modal com passos da suite 028. -4. Painel purge Spec 032 mantém formulário e tabela de códigos activos. -5. Layout responsivo: 1 coluna em mobile (`max-width: 900px`). +1. Grid com **6 cards** de **mesma altura mínima** e alinhamento visual. +2. Cada card: ícone + spec label + título + descrição (2 linhas) + badge + acções. +3. Purge: formulário e tabela **só no modal** «Gerir códigos». +4. OpenPanel / SOC: teste rápido no card; detalhes no modal. +5. Responsivo: 1 coluna em mobile (`max-width: 480px`). --- -## Fora de escopo +## Extensão futura -- Redesign `Infra 2` / SOC dark (`renderInfra2`) -- Dashboard widgets duplicando Infra -- Edição de integrações na UI +O sistema `proc-card` pode ser adoptado em **Serviços** (substituir `servicos-tile` gradualmente) e **Dashboard** — manter o mesmo catálogo de accents e ícones. --- ## Conclusão -A **Spec 033** documenta apenas a **construção visual e layout** da página Infra. Funcionalidades específicas: +**Spec 033** = layout e padronização visual da Infra. Funcionalidades: -- OpenPanel teste → **Spec 028** (+ `CONFIRMACAO-TESTE-API.md`) +- OpenPanel → **Spec 028** - Códigos purge → **Spec 032** -- Webhook SOC → Spec **001** / health integrations +- Webhook SOC → health integrations / Spec **001**