From 41c0c2d42868b3bde19974e2f0e26b21cc9b33fe Mon Sep 17 00:00:00 2001 From: Ligbox Spec Hub Date: Fri, 19 Jun 2026 22:25:17 +0000 Subject: [PATCH] Improve Infra page cards with wizard-style ws-panel aqua/teal layout. Redesign SOC, purge auth, and integration panels; fix servicos-tile-icon CSS typo. --- projects/ops-desk/frontend/assets/app.js | 191 +++++++++------ projects/ops-desk/frontend/assets/styles.css | 230 +++++++++++++++++-- 2 files changed, 338 insertions(+), 83 deletions(-) diff --git a/projects/ops-desk/frontend/assets/app.js b/projects/ops-desk/frontend/assets/app.js index 06e86a3..1ad8c52 100644 --- a/projects/ops-desk/frontend/assets/app.js +++ b/projects/ops-desk/frontend/assets/app.js @@ -3819,6 +3819,12 @@ async function renderInfra2() { } } +function infraKvHtml(items) { + return `
${items.map(([label, value]) => + `
${esc(label)}
${value}
` + ).join('')}
`; +} + async function renderInfra() { const el = document.getElementById('infra-content'); el.innerHTML = '

Verificando…

'; @@ -3832,58 +3838,97 @@ async function renderInfra() { 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 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
  • '; + ).join('') || '
  • Nenhum alerta activo
  • '; el.innerHTML = ` -
    -
    -

    SOC — Integração VM112

    - ${esc(health.status || '—')} +
    +
    +
    + +
    + 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'} +
    -
    -
    Último webhook
    ${last ? esc(last.event) : '—'}
    -
    Domínio
    ${last?.domain ? esc(last.domain) : '—'}
    -
    Há quanto tempo
    ${gap}
    -
    VM112 API
    ${onboard.vm112_api?.reachable ? 'OK' : esc(onboard.vm112_api?.error || 'offline')}
    -
    -
      ${alerts}
    -
    - - +
    +
    +
    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…

    +
    +
    +
    OpenPanel API
    +
    +

    Spec 028 · VM123 bridge :18087 · multidomínio · conta temporária com cleanup.

    +
    + +
    +
    +
    +
    +
    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))}
    +
    +
    -

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

    -
    -
    -

    Códigos autorização purge

    -

    Domínios protegidos (ex.: myvexx.com) exigem código único gerado aqui pelo root — válido para conferência / purge em Serviços.

    -

    A carregar…

    -
    -
    -

    OpenPanel API — Re-engenharia Ligbox

    -

    Spec 028 · VM123 bridge :18087 · multidomínio · conta temporária com cleanup automático.

    -
    - -
    -
    -
    -

    VM112 — Portal Onboard

    -
    -
    HTTP
    ${vm112.http_status ?? '—'}
    -
    Service
    ${esc(vm112.vm112?.service || vm112.error || '—')}
    -
    -
    -
    -

    VM104 — Wazuh SOC

    -
    -
    API
    ${wazuh.http_status ?? '—'}
    -
    Integração
    webhook level ≥ 10 → VM122
    -
    -
    -
    -

    Integrações ativas

    -
    ${esc(JSON.stringify(integrations, null, 2))}
    `; document.getElementById('btn-refresh-health')?.addEventListener('click', () => renderInfra()); document.getElementById('btn-test-webhook')?.addEventListener('click', () => runWebhookIntegrationTest('infra')); @@ -3899,43 +3944,57 @@ async function renderPurgeAuthInfraPanel() { if (!panel) return; try { const meta = await api('/v1/infra/purge-auth-domains'); - const domains = (meta.domains || []).map((d) => `${esc(d)}`).join(', ') || '—'; + const domainChips = (meta.domains || []).map((d) => + `${esc(d)}` + ).join('') || 'Nenhum'; const canGen = meta.can_generate && typeof canManageUsers === 'function' && canManageUsers(); let codesHtml = ''; if (canGen) { const data = await api('/v1/infra/purge-auth-codes?limit=20'); const rows = (data.codes || []).map((c) => ` - ${esc(c.domain)} + ${esc(c.domain)} ${esc(c.note || '—')} ${fmtDate(c.expires_at)} ${esc(c.created_by || '—')} `).join(''); codesHtml = ` +

    Gere código com senha Root — use na conferência antes do purge em Serviços.

    +
    ${domainChips}
    - - - - - - - - - +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +

    -

    Códigos activos

    - - - ${rows || ''} -
    DomínioNotaExpiraPor
    Nenhum código activo
    `; +

    Códigos activos

    +
    + + + ${rows || ''} +
    DomínioNotaExpiraPor
    Nenhum código activo
    +
    `; } else { - codesHtml = '

    Apenas super_admin (root) gera códigos. Peça o código ao root antes do purge em Serviços.

    '; + codesHtml = ` +
    ${domainChips}
    +

    Apenas super_admin (root) gera códigos. Peça o código ao root antes do purge em Serviços.

    `; } - panel.innerHTML = ` -

    Domínios protegidos: ${domains}

    - ${codesHtml}`; + panel.innerHTML = codesHtml; const form = panel.querySelector('#purge-auth-generate-form'); if (form) { form.addEventListener('submit', async (ev) => { diff --git a/projects/ops-desk/frontend/assets/styles.css b/projects/ops-desk/frontend/assets/styles.css index 2603b2a..ed9ab49 100644 --- a/projects/ops-desk/frontend/assets/styles.css +++ b/projects/ops-desk/frontend/assets/styles.css @@ -3771,6 +3771,7 @@ button.health-card { border-radius: 8px; border: 1px dashed #cbd5e1; } +.servicos-tile-icon { font-size: 1.35rem; margin-bottom: 0.35rem; } @@ -3888,29 +3889,224 @@ button.health-card { overflow-y: auto; margin-top: 0.75rem; } -.purge-auth-generated { - margin: 0.75rem 0; - padding: 0.75rem 1rem; - background: rgba(46, 125, 50, 0.12); - border-radius: 6px; -} -.purge-auth-generated.hidden { display: none; } -.purge-auth-code-display { - font-size: 1.25rem; - letter-spacing: 0.08em; -} -.purge-auth-form { - display: grid; - gap: 0.5rem; - max-width: 28rem; - margin-top: 0.75rem; -} .purge-history-removed { font-size: 0.85rem; color: var(--muted, #6b7280); max-width: 14rem; } +/* Infra — layout tipo wizard (ws-panel + aqua/teal) */ +.infra-page { + display: flex; + flex-direction: column; + gap: 0.85rem; +} +.infra-hero { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + align-items: stretch; +} +.infra-hero-chip { + flex: 1 1 200px; + display: flex; + align-items: center; + gap: 0.65rem; + padding: 0.75rem 1rem; + border-radius: 12px; + border: 1px solid #e2e8f0; + background: linear-gradient(135deg, #f0fdfa 0%, #fff 55%); + box-shadow: 0 1px 3px rgba(15, 23, 42, 0.06); +} +.infra-hero-chip--alert { + background: linear-gradient(135deg, #fff7ed 0%, #fff 55%); + border-color: #fed7aa; +} +.infra-hero-dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; + background: #14b8a6; + box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.25); +} +.infra-hero-dot--warn { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.25); } +.infra-hero-dot--bad { background: #ef4444; box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.25); } +.infra-hero-body strong { + display: block; + font-size: 0.88rem; + color: #0f172a; +} +.infra-hero-body span { + display: block; + font-size: 0.75rem; + color: #64748b; + margin-top: 0.1rem; +} +.infra-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.85rem; +} +.infra-panel--wide { grid-column: 1 / -1; } +@media (max-width: 900px) { + .infra-grid { grid-template-columns: 1fr; } +} +.infra-panel .ws-panel-body { padding: 0.85rem 1rem; } +.infra-kv { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.65rem 1rem; + margin: 0; +} +.infra-kv div { + padding: 0.55rem 0.65rem; + border-radius: 8px; + background: #f8fafc; + border: 1px solid #e2e8f0; +} +.infra-kv dt { + font-size: 0.68rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #64748b; + margin: 0; +} +.infra-kv dd { + margin: 0.2rem 0 0; + font-size: 0.9rem; + font-weight: 600; + color: #0f172a; + font-variant-numeric: tabular-nums; +} +.infra-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.75rem; +} +.infra-hint { + margin: 0.65rem 0 0; + font-size: 0.78rem; + color: #64748b; + line-height: 1.45; +} +.infra-alert-list { + list-style: none; + margin: 0.65rem 0 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 0.35rem; +} +.infra-alert-list li { + padding: 0.45rem 0.65rem; + border-radius: 8px; + background: #f8fafc; + border: 1px solid #e2e8f0; + font-size: 0.82rem; +} +.ws-panel-head--aqua { + background: linear-gradient(135deg, #0891b2 0%, #22d3ee 45%, #2dd4bf 100%); +} +.ws-panel-head--slate { + background: linear-gradient(90deg, #334155, #64748b); +} +.ws-panel-head--violet { + background: linear-gradient(90deg, #6d28d9, #8b5cf6); +} +.ws-panel-head--rose { + background: linear-gradient(90deg, #be123c, #f43f5e); +} +.infra-domain-chips { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-bottom: 0.75rem; +} +.infra-domain-chip { + font-size: 0.78rem; + padding: 0.25rem 0.55rem; + border-radius: 999px; + background: #ecfeff; + border: 1px solid #99f6e4; + color: #0f766e; + font-family: ui-monospace, monospace; +} +.purge-auth-form { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.65rem 0.85rem; + margin-top: 0.5rem; + padding: 0.85rem; + border-radius: 10px; + background: linear-gradient(180deg, #f0fdfa 0%, #f8fafc 100%); + border: 1px solid #ccfbf1; +} +.purge-auth-form label { + display: block; + font-size: 0.72rem; + font-weight: 600; + color: #475569; + margin-bottom: 0.25rem; +} +.purge-auth-form input { + width: 100%; + padding: 0.5rem 0.65rem; + border: 1px solid #cbd5e1; + border-radius: 8px; + font: inherit; + background: #fff; +} +.purge-auth-form input:focus { + outline: none; + border-color: #14b8a6; + box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.2); +} +.purge-auth-form button { + grid-column: 1 / -1; + justify-self: start; +} +.purge-auth-generated { + margin: 0.75rem 0; + padding: 1rem 1.1rem; + border-radius: 12px; + background: linear-gradient(135deg, #ecfdf5 0%, #f0fdfa 100%); + border: 1px solid #6ee7b7; + text-align: center; +} +.purge-auth-generated.hidden { display: none; } +.purge-auth-code-display { + font-size: 1.5rem; + letter-spacing: 0.12em; + color: #0f766e; + font-weight: 700; +} +.infra-table-wrap { + margin-top: 0.75rem; + border: 1px solid #e2e8f0; + border-radius: 10px; + overflow: hidden; + background: #fff; +} +.infra-table-wrap table { + margin: 0; +} +.infra-table-wrap th { + background: #f1f5f9; + font-size: 0.68rem; +} +.infra-table-wrap td { + font-size: 0.82rem; +} +.infra-json-panel pre.raw { + margin: 0; + max-height: 240px; + border-radius: 0; + border: none; + background: #0f172a; +} + /* Spec 021 — Acesso utilizador (separado do VM112 Onboard) */ .ws-access-zone { margin-bottom: 1.25rem;