diff --git a/BACKLOG.md b/BACKLOG.md index 644f49c..1acf436 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -1,6 +1,6 @@ # Backlog — Ligbox Ops Platform (VM122) -**Última atualização:** 2026-06-19 (Specs **026–032** purge extra-auth) +**Última atualização:** 2026-06-25 (Console v2 F1 · Spec 019) **Projeto:** `ligbox-ops-platform` **VM122:** `ligbox-ops` · `10.10.10.122` · SSH WAN `:2522` **VM112:** Portal/Wizard — integração **API + webhooks** (fora do compose) @@ -92,6 +92,16 @@ --- +## Backlog futuro (P2–P3) + +| # | Feature | Prioridade | Estado | Notas | +|---|---------|------------|--------|-------| +| **035** | `ligbox-watchman` | **P0** | 📋 | Vigilância contínua + alertas ntfy — incidente Desk 502 | +| **034** | `nextcloud-carbonio-vm112` | P2 | 📋 | Integração Nextcloud — Roger adiou Fase 0 | +| **036** | `backstage-techdocs-speckit-companion` | **P3** | 📋 | Backstage+TechDocs CT130 + Spec Kit Companion LAPTOP — spec escrita, **sem implementação** | + +--- + ## Track A — Auditoria & Ops Dashboard | ID | P | Item | Estado | @@ -100,6 +110,7 @@ | **OPS-2** | P0 | `docker-compose.mvp.yml` | ✅ | | **OPS-3** | P0 | `tenant-registry` (VM112 = 1º nó) | ✅ | | **OPS-7** | P1 | VM123 finance stack (Spec 024) | ✅ | +| **OPS-8** | P1 | Console v2 F1 — home unificada `console.ligbox.com.br` (Spec 019) | ✅ 2026-06-25 | | **AUD-1** | P0 | Collectors: Carbonio, DNS, nginx | 🔄 parcial | | **AUD-2** | P0 | UI `/ops/overview` + API scorecard | 🔄 parcial | | **AUD-3** | P1 | Scorecard por domínio (8 checks) | 🔄 | diff --git a/docs/network/VM123_INVENTARIO.md b/docs/network/VM123_INVENTARIO.md index 0c6ecbc..2a68e32 100644 --- a/docs/network/VM123_INVENTARIO.md +++ b/docs/network/VM123_INVENTARIO.md @@ -1,6 +1,6 @@ # VM123 — inventário Ops Console -**Data:** 2026-06-17 +**Data:** 2026-06-25 (actualizado Console v2 F1) **Host:** `vm123-finance` · `10.10.10.123` ## Serviços existentes (não alterados) @@ -23,7 +23,9 @@ | Container | `ligbox-ops-console-ui` | | Porta | `0.0.0.0:8100` (LAN — Traefik CT114) | | Health | `curl http://10.10.10.123:8100/health` → `ok` | -| URL pública | `https://console.ligbox.com.br` → **✅ G1 2026-06-25** | +| URL pública | `https://console.ligbox.com.br` → **✅ G1 + v2 F1 2026-06-25** | +| Versão UI | Console v2 Fase 1 — KPI, Saúde, Incidentes, Domínios | +| Rollback | `specs/019-.../deploy/CONSOLE-V2-ROLLBACK.md` | | Rede Docker | `ligbox-console` (bridge dedicada) | | Imagem | `ligbox/ops-console:latest` | diff --git a/projects/ops-desk/frontend/assets/styles.css b/projects/ops-desk/frontend/assets/styles.css index 2e4afcf..252c94a 100644 --- a/projects/ops-desk/frontend/assets/styles.css +++ b/projects/ops-desk/frontend/assets/styles.css @@ -4557,6 +4557,21 @@ button.health-card { .migration-gate-ready { color: #22c55e; } .migration-gate-blocked { color: #ef4444; } .migration-gate-warning { color: #f59e0b; } + +/* Console v2 migration banners (Desk → console.ligbox.com.br) */ +.console-v2-banner { + margin: 0 0 1rem; + padding: 0.65rem 1rem; + background: linear-gradient(90deg, #e6f4ff 0%, #f0f7ff 100%); + border: 1px solid #91caff; + border-radius: 8px; + font-size: 0.85rem; + color: #1a1c21; +} +.console-v2-banner a { + color: #006bb4; + font-weight: 600; +} .billing-modal-backdrop { position: fixed; inset: 0; background: #0009; z-index: 900; display: flex; align-items: center; justify-content: center; } .billing-modal { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 1.25rem; max-width: 480px; width: 90%; } .ticket-card-aside { diff --git a/projects/ops-desk/frontend/index.html b/projects/ops-desk/frontend/index.html index 10f45da..75afb98 100644 --- a/projects/ops-desk/frontend/index.html +++ b/projects/ops-desk/frontend/index.html @@ -275,10 +275,19 @@
+
+ Console v2 — KPIs e ranking técnico migram para + console.ligbox.com.br. + Este painel permanece até cutover Fase 4. +

Carregando…

+
+ Saúde migrada — scorecard e domínios com atenção estão no + Console unificado. +

Carregando…

@@ -291,6 +300,10 @@
+
+ Chamados CH-* — investigação unificada em + console.ligbox.com.br/chamados. +
@@ -312,6 +325,10 @@
+
+ Discover — feed de eventos Wazuh/onboard em + console.ligbox.com.br/discover. +
diff --git a/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-EXEC-20260625.md b/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-EXEC-20260625.md new file mode 100644 index 0000000..cdf9c9e --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-EXEC-20260625.md @@ -0,0 +1,31 @@ +# Console v2 Fase 1 — Execução 2026-06-25 + +**Responsável:** Cursor Agent · **Aprovado por:** Roger + +## Entregas + +1. Home unificada em `console.ligbox.com.br/` (KPI → Alertas → Saúde → Incidentes → Domínios → Infra → Chamados) +2. Componentes React: `KpiBar`, `HealthCards`, `DomainAttention`, `IncidentsStrip`, `ShortcutsRow` +3. Loader `lib/console-home.js` com fallback mock + API JWT quando disponível +4. Desk VM122: banners migração Dashboard/Overview/Eventos/Tickets +5. Documentação + rollback `CONSOLE-V2-ROLLBACK.md` + +## Backups pré-deploy + +- VM123: `frontend.bak-v2-*`, tag Docker `ligbox/ops-console:pre-v2-YYYYMMDD` +- Git: commit anterior documentado no rollback + +## Validação + +```bash +curl -sS https://console.ligbox.com.br/health +curl -sS https://console.ligbox.com.br/ | head -20 +``` + +## Pendente Fase 2+ + +- Rota `/ops` alias (opcional) +- Assist panel live (T052) +- API chamados real sem mock +- Cutover 301 desk → console +- Mission board `/ops/agentic` diff --git a/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-ROLLBACK.md b/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-ROLLBACK.md new file mode 100644 index 0000000..ecb52c5 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/CONSOLE-V2-ROLLBACK.md @@ -0,0 +1,80 @@ +# Console v2 — Rollback (Fase 1) + +**Data:** 2026-06-25 +**Spec:** 019 · **Exec:** CONSOLE-V2-F1-20260625 +**Ambiente:** VM123 (`10.10.10.123`) · CT114 Traefik · `console.ligbox.com.br` + +--- + +## O que mudou (Fase 1) + +| Área | Alteração | +|------|-----------| +| Console home | KPI bar, Saúde plataforma (ex-Overview), Domínios com atenção, Incidentes Agentic, Atalhos | +| Desk VM122 | Banners de migração em Dashboard, Overview, Eventos, Tickets | +| Repo | `specs/019-.../deploy/frontend/src/*` + docs | + +**Não alterado nesta fase:** CT114 Traefik, DNS, `/etc/network/interfaces`, VM112, rota `/ops` (home continua em `/`). + +--- + +## Pré-requisitos rollback + +Backups criados **antes** do deploy v2: + +```bash +ssh root@10.10.10.123 +ls -la /opt/ligbox-ops-console/frontend.bak-v2-* +docker images | grep ops-console +``` + +Git no CT130: + +```bash +cd /opt/ligbox-spec-hub/repos/ligbox-ops-platform +git log --oneline -5 +``` + +--- + +## Rollback rápido — só frontend VM123 + +```bash +ssh root@10.10.10.123 +cd /opt/ligbox-ops-console +BACKUP=$(ls -td frontend.bak-v2-* 2>/dev/null | head -1) +if [ -n "$BACKUP" ]; then + rm -rf frontend + cp -a "$BACKUP" frontend +fi +docker compose build --no-cache && docker compose up -d +curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8100/health +``` + +--- + +## Rollback imagem Docker + +```bash +docker tag ligbox/ops-console:pre-v2-YYYYMMDD ligbox/ops-console:latest +docker compose up -d +``` + +--- + +## Rollback código (CT130) + +```bash +cd /opt/ligbox-spec-hub/repos/ligbox-ops-platform +git revert --no-edit +./portal/refresh-spec-driver.sh +rsync -a specs/019-ops-console-active-operations/ \ + /opt/ligbox-spec-hub/obsidian-vault/ligbox-ops-platform/specs/019-ops-console-active-operations/ +``` + +--- + +## Referências + +- G1 Traefik: `deploy/traefik/G1-ROLLBACK.md` +- Inventário: `docs/network/VM123_INVENTARIO.md` diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/components/DomainAttention.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/components/DomainAttention.jsx new file mode 100644 index 0000000..98539f2 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/components/DomainAttention.jsx @@ -0,0 +1,38 @@ +import { Link } from 'react-router-dom' + +export default function DomainAttention({ rows }) { + if (!rows?.length) { + return null + } + + return ( +
+

Domínios com atenção

+

+ Issues do antigo Overview Desk — clique para abrir chamado ou investigar. +

+ + + + + + + + {rows.map((r) => ( + + + + + + + + ))} + +
DomínioTenantCheckMensagemAcção
{r.domain}{r.tenant_name}{r.check_id}{r.message} + + Chamados + +
+
+ ) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/components/HealthCards.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/components/HealthCards.jsx new file mode 100644 index 0000000..6b41996 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/components/HealthCards.jsx @@ -0,0 +1,67 @@ +function statusClass(status) { + if (status === 'healthy') return 'health-ok' + if (status === 'degraded') return 'health-warn' + if (status === 'critical') return 'health-bad' + return 'health-unknown' +} + +function statusLabel(status) { + if (status === 'healthy') return 'Saudável' + if (status === 'degraded') return 'Degradado' + if (status === 'critical') return 'Crítico' + return 'Desconhecido' +} + +export default function HealthCards({ tenants }) { + if (!tenants?.length) { + return ( +
+

Nenhum tenant auditado. Aguarde ciclo de auditoria ou complete onboarding.

+
+ ) + } + + return ( +
+ {tenants.map((t) => { + const pass = t.score?.pass ?? 0 + const total = t.score?.total ?? 8 + const warn = t.score?.warn ?? 0 + const fail = t.score?.fail ?? 0 + const cls = statusClass(t.status) + return ( +
+
+
+

{t.name}

+

+ {t.ip} + {' · '} + {t.domains_count ?? 0} domínio(s) +

+
+ {statusLabel(t.status)} +
+

+ {pass}/{total} pass · {warn} warn · {fail} fail +

+ {t.kind === 'wazuh_soc' && t.alerts_count != null ? ( +

Alertas Wazuh: {t.alerts_count} · max L{t.max_level ?? '—'}

+ ) : null} + {t.top_issues?.length ? ( +
    + {t.top_issues.slice(0, 3).map((i) => ( +
  • + {i.domain} · {i.check_id} — {i.message || i.status} +
  • + ))} +
+ ) : ( +

Sem issues críticas no último audit

+ )} +
+ ) + })} +
+ ) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/components/IncidentsStrip.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/components/IncidentsStrip.jsx new file mode 100644 index 0000000..3ee21f9 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/components/IncidentsStrip.jsx @@ -0,0 +1,29 @@ +import { Link } from 'react-router-dom' + +export default function IncidentsStrip({ incidents }) { + if (!incidents?.length) return null + + return ( +
+

Incidentes abertos (Agentic Ops)

+
+ {incidents.map((inc) => ( + +
+ {inc.title} +

{inc.scenario_id} · {inc.severity} · visto {inc.occurrence_count ?? 1}×

+
+ Investigar → + + ))} +
+

+ Mission board completo: desk.ligbox.com.br (migra para /ops/agentic) +

+
+ ) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/components/KpiBar.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/components/KpiBar.jsx new file mode 100644 index 0000000..e78c2d1 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/components/KpiBar.jsx @@ -0,0 +1,24 @@ +import { Link } from 'react-router-dom' + +const KPI_CONFIG = [ + { key: 'tickets_open', label: 'CH / tickets abertos', to: '/chamados', variant: 'blue' }, + { key: 'tickets_escalated', label: 'Escalados', to: '/chamados', variant: 'amber' }, + { key: 'wazuh_high', label: 'Wazuh L12+', to: '/discover?bucket=high', variant: 'red', compute: (s, alerts) => (alerts?.high || 0) + (alerts?.critical || 0) }, + { key: 'webhook_events', label: 'Eventos 48h', to: '/discover', variant: 'slate', compute: (s) => s.webhook_events ?? '—' }, +] + +export default function KpiBar({ summary, alertCounts }) { + return ( +
+ {KPI_CONFIG.map(({ key, label, to, variant, compute }) => { + const value = compute ? compute(summary, alertCounts) : (summary?.[key] ?? '—') + return ( + + {value} + {label} + + ) + })} +
+ ) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/components/ShortcutsRow.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/components/ShortcutsRow.jsx new file mode 100644 index 0000000..4649176 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/components/ShortcutsRow.jsx @@ -0,0 +1,28 @@ +import { Link } from 'react-router-dom' + +const SHORTCUTS = [ + { to: '/discover', icon: '🔍', title: 'Discover', desc: 'Feed unificado de eventos' }, + { to: '/chamados', icon: '📋', title: 'Chamados', desc: 'Hub CH-* investigação' }, + { to: '/discover?bucket=medium', icon: '🛡️', title: 'Alertas médios', desc: 'L7–L11 últimas 24h' }, +] + +export default function ShortcutsRow() { + return ( +
+

Atalhos

+
+ {SHORTCUTS.map((s) => ( + +
+
{s.icon}
+
+

{s.title}

+

{s.desc}

+
+
+ + ))} +
+
+ ) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/lib/console-home.js b/specs/019-ops-console-active-operations/deploy/frontend/src/lib/console-home.js new file mode 100644 index 0000000..4fa7320 --- /dev/null +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/lib/console-home.js @@ -0,0 +1,78 @@ +import { + MOCK_AUDIT_OVERVIEW, + MOCK_CHAMADOS, + MOCK_INCIDENTS, + MOCK_SUMMARY, +} from '../mock' +import { apiGet } from './api' +import { loadDiscoverEvents } from './events' +import { aggregateWazuh24h } from './severity' + +export async function loadConsoleHomeData() { + let fromApi = false + const result = { + summary: { ...MOCK_SUMMARY }, + audit: { tenants: [...MOCK_AUDIT_OVERVIEW.tenants] }, + incidents: [...MOCK_INCIDENTS], + chamados: [...MOCK_CHAMADOS], + alertCounts: { critical: 0, high: 0, medium: 0, low: 0 }, + fromApi: false, + } + + const { events, fromApi: eventsApi } = await loadDiscoverEvents() + result.alertCounts = aggregateWazuh24h(events) + if (eventsApi) fromApi = true + + try { + const summary = await apiGet('/api/v1/desk/summary') + if (summary) { + result.summary = summary + if (summary.recent_tickets?.length) { + result.chamados = summary.recent_tickets.map((t) => ({ + public_id: t.chamado_public_id || `TK-${t.id}`, + status: t.status, + domain: t.domain || '—', + max_severity: t.severity, + })) + } + fromApi = true + } + } catch { /* mock */ } + + try { + const audit = await apiGet('/api/v1/audit/overview') + if (audit?.tenants?.length) { + result.audit = audit + fromApi = true + } + } catch { /* mock */ } + + try { + const inc = await apiGet('/api/v1/agents/incidents?status=open&limit=8') + if (inc?.incidents?.length) { + result.incidents = inc.incidents + fromApi = true + } + } catch { /* mock */ } + + result.fromApi = fromApi + return result +} + +/** Flatten top_issues from all audit tenants for domain attention list */ +export function collectDomainAttention(tenants, limit = 12) { + const rows = [] + for (const t of tenants || []) { + for (const issue of t.top_issues || []) { + rows.push({ + tenant_id: t.tenant_id, + tenant_name: t.name, + domain: issue.domain, + check_id: issue.check_id, + message: issue.message || issue.status, + status: issue.status, + }) + } + } + return rows.slice(0, limit) +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/mock.js b/specs/019-ops-console-active-operations/deploy/frontend/src/mock.js index 5f86b9f..a49a362 100644 --- a/specs/019-ops-console-active-operations/deploy/frontend/src/mock.js +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/mock.js @@ -66,3 +66,91 @@ export const MOCK_AUDIT_TENANTS = [ { id: 'wazuh', label: 'Wazuh SOC', kind: 'wazuh_soc', score: 8 }, { id: 'demo', label: 'cenario-demo.ops.ligbox.com.br', kind: 'domain', score: 6, domain: 'cenario-demo.ops.ligbox.com.br' }, ] + +export const MOCK_SUMMARY = { + tickets_open: 4, + tickets_escalated: 1, + tickets_assisting: 2, + tickets_closed: 18, + webhook_events: 142, + wazuh_events: 89, + tenants: 4, + billing_pending: 2, + billing_active: 11, +} + +/** Formato /api/v1/audit/overview — espelha VM122 audit_store */ +export const MOCK_AUDIT_OVERVIEW = { + generated_at: '2026-06-25T12:00:00Z', + tenants: [ + { + tenant_id: 1, + name: 'VM112 Onboard / Carbonio', + ip: '10.10.10.112', + status: 'degraded', + score: { pass: 7, warn: 1, fail: 0, total: 8 }, + domains_count: 3, + last_audit_at: '2026-06-25T08:30:00Z', + top_issues: [ + { domain: 'myvexx.com', check_id: 'dmarc', message: 'DMARC policy none', status: 'warn' }, + { domain: 'diarissima.com', check_id: 'tls_webmail', message: 'cert expira em 12d', status: 'warn' }, + ], + }, + { + tenant_id: 2, + name: 'VM122 Ligbox Ops', + ip: '10.10.10.122', + status: 'healthy', + score: { pass: 8, warn: 0, fail: 0, total: 8 }, + domains_count: 2, + last_audit_at: '2026-06-25T09:00:00Z', + top_issues: [], + }, + { + tenant_id: 3, + name: 'VM104 Wazuh SOC', + ip: '10.10.10.104', + kind: 'wazuh_soc', + status: 'healthy', + score: { pass: 8, warn: 0, fail: 0, total: 8 }, + alerts_count: 14, + max_level: 9, + domains_count: 0, + top_issues: [], + }, + { + tenant_id: 4, + name: 'cenario-demo', + ip: '10.10.10.112', + status: 'critical', + score: { pass: 4, warn: 2, fail: 2, total: 8 }, + domains_count: 1, + last_audit_at: '2026-06-24T22:00:00Z', + top_issues: [ + { domain: 'cenario-demo.ops.ligbox.com.br', check_id: 'mx', message: 'MX não resolve', status: 'fail' }, + { domain: 'cenario-demo.ops.ligbox.com.br', check_id: 'spf', message: 'SPF missing', status: 'fail' }, + ], + }, + ], +} + +export const MOCK_INCIDENTS = [ + { + id: 1, + scenario_id: 'desk.api.health', + title: 'desk.api.health — HTTP 502 upstream', + severity: 'critical', + status: 'open', + occurrence_count: 3, + last_seen_at: '2026-06-25T11:40:00Z', + }, + { + id: 2, + scenario_id: 'onboard.dns.stale', + title: 'Onboard DNS apply stale > 2h', + severity: 'warn', + status: 'open', + occurrence_count: 1, + last_seen_at: '2026-06-25T10:15:00Z', + }, +] diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/theme.css b/specs/019-ops-console-active-operations/deploy/frontend/src/theme.css index fcd3342..18a20f7 100644 --- a/specs/019-ops-console-active-operations/deploy/frontend/src/theme.css +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/theme.css @@ -369,3 +369,138 @@ code { .tenant-health-ok { color: var(--lb-sev-low); font-weight: 600; } .tenant-health-warn { color: var(--lb-sev-med); font-weight: 600; } .tenant-health-bad { color: var(--lb-sev-high); font-weight: 600; } + +/* Console v2 — KPI bar */ +.kpi-bar { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.75rem; + margin-bottom: 1.25rem; +} +@media (max-width: 900px) { .kpi-bar { grid-template-columns: repeat(2, 1fr); } } + +.kpi-card { + display: block; + text-decoration: none; + color: inherit; + background: var(--lb-bg-card); + border: 1px solid var(--lb-border); + border-radius: var(--lb-radius); + padding: 0.9rem 1rem; + box-shadow: var(--lb-shadow); + transition: border-color 0.15s, transform 0.1s; +} +.kpi-card:hover { + border-color: var(--lb-accent); + text-decoration: none; + transform: translateY(-1px); +} +.kpi-value { + display: block; + font-size: 1.65rem; + font-weight: 700; + line-height: 1.1; + color: var(--lb-text-heading); +} +.kpi-label { + display: block; + font-size: 0.72rem; + color: var(--lb-text-muted); + margin-top: 0.35rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} +.kpi-card--red .kpi-value { color: var(--lb-sev-high); } +.kpi-card--amber .kpi-value { color: var(--lb-sev-med); } +.kpi-card--blue .kpi-value { color: var(--lb-accent); } + +/* Health cards (Overview port) */ +.health-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1rem; +} +.health-card { + background: var(--lb-bg-card); + border: 1px solid var(--lb-border); + border-radius: var(--lb-radius); + padding: 1rem 1.1rem; + box-shadow: var(--lb-shadow); + border-left: 4px solid var(--lb-border); +} +.health-card--health-ok { border-left-color: var(--lb-sev-low); } +.health-card--health-warn { border-left-color: var(--lb-sev-med); } +.health-card--health-bad { border-left-color: var(--lb-sev-high); } +.health-card-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 0.75rem; + margin-bottom: 0.5rem; +} +.health-card h3 { + margin: 0; + font-size: 0.95rem; + font-weight: 700; +} +.health-card-meta { + margin: 0.25rem 0 0; + font-size: 0.78rem; + color: var(--lb-text-secondary); +} +.health-score-line { + margin: 0 0 0.5rem; + font-size: 0.82rem; + font-weight: 600; +} +.health-badge { + font-size: 0.68rem; + font-weight: 700; + text-transform: uppercase; + padding: 0.2rem 0.45rem; + border-radius: 4px; + white-space: nowrap; +} +.health-badge--health-ok { background: #e8f5e9; color: #2e7d32; } +.health-badge--health-warn { background: #fff8e1; color: #f57c00; } +.health-badge--health-bad { background: #fce8e6; color: var(--lb-sev-high); } +.health-badge--health-unknown { background: var(--lb-bg-subtle); color: var(--lb-text-muted); } +.health-issue-list { + margin: 0; + padding-left: 1.1rem; + font-size: 0.78rem; + color: var(--lb-text-secondary); +} + +/* Agentic incidents strip */ +.incident-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.incident-card { + display: flex; + justify-content: space-between; + align-items: center; + gap: 1rem; + padding: 0.75rem 1rem; + background: var(--lb-bg-card); + border: 1px solid var(--lb-border); + border-radius: var(--lb-radius); + text-decoration: none; + color: inherit; +} +.incident-card:hover { border-color: var(--lb-accent); text-decoration: none; } +.incident-card p { + margin: 0.25rem 0 0; + font-size: 0.78rem; + color: var(--lb-text-secondary); +} +.incident-card--critical { border-left: 4px solid var(--lb-sev-critical); } +.incident-card--warn { border-left: 4px solid var(--lb-sev-med); } +.incident-cta { + font-size: 0.78rem; + font-weight: 600; + color: var(--lb-accent); + white-space: nowrap; +} diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/views/ConsoleHome.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/views/ConsoleHome.jsx index 08fa5aa..67bb65c 100644 --- a/specs/019-ops-console-active-operations/deploy/frontend/src/views/ConsoleHome.jsx +++ b/specs/019-ops-console-active-operations/deploy/frontend/src/views/ConsoleHome.jsx @@ -1,39 +1,21 @@ import { useEffect, useState } from 'react' import { Link } from 'react-router-dom' import AlertBar from '../components/AlertBar' -import { loadDiscoverEvents } from '../lib/events' -import { aggregateWazuh24h } from '../lib/severity' -import { MOCK_AUDIT_TENANTS, MOCK_CHAMADOS, MOCK_TENANTS } from '../mock' +import DomainAttention from '../components/DomainAttention' +import HealthCards from '../components/HealthCards' +import IncidentsStrip from '../components/IncidentsStrip' +import KpiBar from '../components/KpiBar' +import ShortcutsRow from '../components/ShortcutsRow' +import { collectDomainAttention, loadConsoleHomeData } from '../lib/console-home' +import { MOCK_TENANTS } from '../mock' import { apiGet } from '../lib/api' -function FeatureSection({ title, children }) { - return ( -
-

{title}

-
{children}
-
- ) -} - -function FeatureCard({ icon, title, description, to }) { - const inner = ( -
- -
-

{title}

-

{description}

-
-
- ) - return to ? {inner} : inner -} - function InfraTable({ tenants }) { return (

Nós de infraestrutura

- Registo de VMs e serviços Ligbox (antigo menu Tenants). + Registo de VMs e serviços Ligbox (antigo menu Tenants no Desk).

@@ -57,131 +39,69 @@ function InfraTable({ tenants }) { ) } -function AuditTenantCards({ auditTenants }) { - if (!auditTenants.length) return null - return ( -
-

Saúde por tenant / domínio

-
- {auditTenants.map((t) => { - const score = t.score ?? t.health_score - const cls = score >= 8 ? 'tenant-health-ok' : score >= 5 ? 'tenant-health-warn' : 'tenant-health-bad' - return ( -
-
📧
-
-

{t.label || t.domain || t.name}

-

- {t.kind ? {t.kind} : null}{' '} - {score != null ? `${score}/8 checks` : t.status || 'monitorizar'} -

-
-
- ) - })} -
-
- ) -} - export default function ConsoleHome() { const [infra, setInfra] = useState(MOCK_TENANTS) - const [audit, setAudit] = useState(MOCK_AUDIT_TENANTS) - const [alertCounts, setAlertCounts] = useState({ critical: 0, high: 0, medium: 0, low: 0 }) - const [usingMock, setUsingMock] = useState(true) + const [home, setHome] = useState(null) + const [loading, setLoading] = useState(true) useEffect(() => { let cancelled = false ;(async () => { + const data = await loadConsoleHomeData() + if (cancelled) return + setHome(data) + setLoading(false) try { - const { events, fromApi } = await loadDiscoverEvents() - if (!cancelled) { - setAlertCounts(aggregateWazuh24h(events)) - if (fromApi) setUsingMock(false) - } - const [tData, aData] = await Promise.all([ - apiGet('/api/v1/tenants').catch(() => null), - apiGet('/api/v1/audit/overview').catch(() => null), - ]) - if (cancelled) return - if (tData?.tenants?.length) { - setInfra(tData.tenants) - setUsingMock(false) - } - if (aData?.tenants?.length) { - setAudit(aData.tenants) - setUsingMock(false) - } - } catch { - /* mock fallback */ - } + const tData = await apiGet('/api/v1/tenants') + if (!cancelled && tData?.tenants?.length) setInfra(tData.tenants) + } catch { /* mock infra */ } })() return () => { cancelled = true } }, []) + const auditTenants = home?.audit?.tenants || [] + const domainRows = collectDomainAttention(auditTenants) + const usingMock = home ? !home.fromApi : true + return ( <>

Console

- Operações unificadas — clique nos números de alerta para ver quem gerou cada evento (agente, regra, domínio). - {usingMock ? ' Dados de demonstração até login API.' : ''} + Home unificada — KPIs, saúde da plataforma, alertas Wazuh 24h e infraestrutura. + {usingMock ? ' Dados de demonstração até login API (JWT Desk).' : ' Dados live da API ops.'}

- + {loading ?

A carregar indicadores…

: null} - - - - - + {home ? ( + <> + + - - - - - +
+

Saúde da plataforma

+ +
- - + + + + -
-

Chamados recentes

-
    - {MOCK_CHAMADOS.map((c) => ( -
  • - {c.public_id} — {c.domain}{' '} - {c.max_severity ? L{c.max_severity} : null} -
  • - ))} -
-
+
+

Chamados recentes

+
    + {home.chamados.map((c) => ( +
  • + {c.public_id} — {c.domain}{' '} + {c.max_severity ? L{c.max_severity} : null} + {c.status ? {c.status} : null} +
  • + ))} +
+
+ + ) : null} ) } diff --git a/specs/019-ops-console-active-operations/design/navigation-ia.md b/specs/019-ops-console-active-operations/design/navigation-ia.md index 5ff4ec4..370cd42 100644 --- a/specs/019-ops-console-active-operations/design/navigation-ia.md +++ b/specs/019-ops-console-active-operations/design/navigation-ia.md @@ -33,11 +33,34 @@ O **Chamado `CH-*` é o hub de investigação** — equivalente ao «caso» TheH --- -## Módulos sidebar (Console VM123) +## Módulos sidebar (Console VM123) — **v2 Fase 1 (2026-06-25)** + +| # | Módulo | Função | +|---|--------|--------| +| 1 | **Console** | Home unificada: KPIs, alertas 24h, saúde plataforma, incidentes Agentic, domínios, infra, chamados | +| 2 | **Discover** | Feed eventos; alertas clicáveis por severidade | +| 3 | **Chamados** | Lista + hub `CH-*` | + +**Deprecado no Desk (banners Fase 4 leve):** Overview, Dashboard KPIs, Eventos, Tickets → links para Console. + +```text +┌─────────────────────────────────────────┐ +│ Console (home /) │ +│ KPI bar → Alertas 24h → Saúde tenants │ +│ → Incidentes Agentic → Domínios atenção │ +│ → Infra (ex-Tenants) → Chamados recentes│ +└──────────────┬──────────────────────────┘ + │ + Discover / Chamados / Hub CH-* +``` + +--- + +## Módulos sidebar (legado — pré-v2) | # | Módulo | Inspiração Wazuh | Função | |---|--------|------------------|--------| -| 1 | **Overview** | Threat Hunting overview | Entrada rápida; não substitui o hub | +| 1 | **Overview** | Threat Hunting overview | ~~Entrada rápida~~ → fundido em Console | | 2 | **Discover** | Explore → Discover | Investigação transversal; sempre liga a `CH-*` | | 3 | **Chamados** | — (Ligbox) | Lista de hubs activos; fila de trabalho | | 4 | **Hub `CH-*`** | Alert detail + case | **Ecrã principal de investigação** (rota `/chamados/CH-…`) | diff --git a/specs/019-ops-console-active-operations/tasks.md b/specs/019-ops-console-active-operations/tasks.md index 276dc22..65808dc 100644 --- a/specs/019-ops-console-active-operations/tasks.md +++ b/specs/019-ops-console-active-operations/tasks.md @@ -54,6 +54,17 @@ - [x] T040 Deploy Docker VM123 — **ligbox-ops-console-ui UP :8100** - [x] T041 Traefik console.ligbox.com.br **(G1 2026-06-25 — HTTPS 200 via CT114)** +## F4 — Console v2 home (2026-06-25) + +- [x] T047 KPI bar (desk/summary + alertas Wazuh) +- [x] T048 Saúde plataforma — port Overview (`HealthCards`) +- [x] T049 Domínios com atenção (`DomainAttention`) +- [x] T050 Incidentes Agentic strip (mock + API fallback) +- [x] T051 Desk banners migração Overview/Eventos/Tickets/Dashboard +- [x] T052 Rollback doc `deploy/CONSOLE-V2-ROLLBACK.md` +- [ ] T053 Rota `/ops` alias + redirect desk cutover +- [ ] T054 Assist panel live no hub CH-* + --- ## F4 — Runbooks + aprovações