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
new file mode 100644
index 0000000..85e7c9b
--- /dev/null
+++ b/specs/019-ops-console-active-operations/deploy/frontend/src/views/ConsoleHome.jsx
@@ -0,0 +1,197 @@
+import { useEffect, useState } from 'react'
+import { Link } from 'react-router-dom'
+import { MOCK_AUDIT_TENANTS, MOCK_CHAMADOS, MOCK_TENANTS } from '../mock'
+import { apiGet } from '../lib/api'
+
+function AlertBar() {
+ return (
+
+ {[
+ ['0', 'Critical severity', 'wz-sev-critical', 'Rule level 15 or higher'],
+ ['0', 'High severity', 'wz-sev-high', 'Rule level 12 to 14'],
+ ['2', 'Medium severity', 'wz-sev-med', 'Rule level 7 to 11'],
+ ['12', 'Low severity', 'wz-sev-low', 'Rule level 0 to 6'],
+ ].map(([val, label, cls, hint]) => (
+
+ {val}
+ {label}
+ {hint}
+
+ ))}
+
+ )
+}
+
+function FeatureSection({ title, children }) {
+ return (
+
+ )
+}
+
+function FeatureCard({ icon, title, description, to }) {
+ const inner = (
+
+
{icon}
+
+
{title}
+
{description}
+
+
+ )
+ return to ?
{inner} : inner
+}
+
+function InfraTable({ tenants }) {
+ return (
+
+
Nós de infraestrutura
+
+ Registo de VMs e serviços Ligbox (antigo menu Tenants).
+
+
+
+
+ | ID | Nome | IP | Papel | Desde |
+
+
+
+ {tenants.map((t) => (
+
+ | {t.id} |
+ {t.name} |
+ {t.ip} |
+ {t.role} |
+ {t.created_at ? new Date(t.created_at).toLocaleDateString('pt-BR') : '—'} |
+
+ ))}
+
+
+
+ )
+}
+
+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 [usingMock, setUsingMock] = useState(true)
+
+ useEffect(() => {
+ let cancelled = false
+ ;(async () => {
+ try {
+ 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 */
+ }
+ })()
+ return () => { cancelled = true }
+ }, [])
+
+ return (
+ <>
+
Console
+
+ Operações unificadas — chamados, discover, tenants de infraestrutura e saúde de domínios num só lugar.
+ {usingMock ? ' (dados de demonstração — login API em breve)' : ''}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Chamados recentes
+
+ {MOCK_CHAMADOS.map((c) => (
+ -
+ {c.public_id} — {c.domain}{' '}
+ {c.max_severity ? L{c.max_severity} : null}
+
+ ))}
+
+
+ >
+ )
+}
diff --git a/specs/019-ops-console-active-operations/deploy/frontend/src/views/Discover.jsx b/specs/019-ops-console-active-operations/deploy/frontend/src/views/Discover.jsx
index 04c408b..18ceb29 100644
--- a/specs/019-ops-console-active-operations/deploy/frontend/src/views/Discover.jsx
+++ b/specs/019-ops-console-active-operations/deploy/frontend/src/views/Discover.jsx
@@ -4,18 +4,18 @@ import { MOCK_EVENTS } from '../mock'
export default function Discover() {
return (
<>
-
Discover
-
Feed unificado — click abre hub CH-* (≤ 2 cliques)
-
-
+ Discover
+ Feed unificado — clique num evento para abrir o hub CH-* (≤ 2 cliques).
+
+
-
+
| ID | Origem | Evento | Domínio | Sev | Chamado |
{MOCK_EVENTS.map((e) => (
-
+
| {e.id} |
{e.source} |
{e.event_type} |
diff --git a/specs/019-ops-console-active-operations/design/tokens.css b/specs/019-ops-console-active-operations/design/tokens.css
index 1870cd4..8b2c8d2 100644
--- a/specs/019-ops-console-active-operations/design/tokens.css
+++ b/specs/019-ops-console-active-operations/design/tokens.css
@@ -1,44 +1,49 @@
-/* Ligbox Ops Console — design tokens (referência Wazuh 4.x SOC) */
+/* Ligbox Console — tokens claros (referência Wazuh 4.x home) */
:root {
- /* Surfaces */
- --lb-bg-app: #0f1219;
- --lb-bg-sidebar: #141824;
- --lb-bg-panel: #1a1f2e;
- --lb-bg-card: #252b3b;
- --lb-bg-hover: #2d3448;
- --lb-border: #3d4559;
+ /* Surfaces — light */
+ --lb-bg-app: #f5f7fa;
+ --lb-bg-sidebar: #ffffff;
+ --lb-bg-panel: #ffffff;
+ --lb-bg-card: #ffffff;
+ --lb-bg-hover: #e8ecf4;
+ --lb-bg-subtle: #fafbfd;
+ --lb-border: #d3dae6;
+ --lb-border-light: #e4e8f0;
/* Brand */
- --lb-accent: #1565d8;
- --lb-accent-hover: #1e88e5;
+ --lb-accent: #006bb4;
+ --lb-accent-hover: #005a9c;
+ --lb-accent-soft: #e6f0fa;
/* Text */
- --lb-text-primary: #e8eaed;
- --lb-text-secondary: #9aa0a6;
- --lb-text-muted: #6b7280;
+ --lb-text-primary: #1a1c21;
+ --lb-text-secondary: #535966;
+ --lb-text-muted: #69707d;
+ --lb-text-heading: #343741;
/* Severity (Wazuh levels) */
- --lb-sev-low: #4caf50;
- --lb-sev-med: #ff9800;
- --lb-sev-high: #f44336;
- --lb-sev-critical: #b71c1c;
+ --lb-sev-low: #017d73;
+ --lb-sev-med: #f5a700;
+ --lb-sev-high: #bd2717;
+ --lb-sev-critical: #8a0f0f;
/* Source badges */
--lb-src-wazuh: #00a9e0;
- --lb-src-onboard: #7c4dff;
- --lb-src-support: #26a69a;
- --lb-src-audit: #78909c;
+ --lb-src-onboard: #6b4c9a;
+ --lb-src-support: #017d73;
+ --lb-src-audit: #69707d;
/* Chamado status */
--lb-st-novo: #5c6bc0;
- --lb-st-assumido: #29b6f6;
- --lb-st-correcao: #ffa726;
- --lb-st-resolvido: #66bb6a;
+ --lb-st-assumido: #006bb4;
+ --lb-st-correcao: #f5a700;
+ --lb-st-resolvido: #017d73;
/* Layout */
- --lb-sidebar-width: 240px;
+ --lb-sidebar-width: 260px;
--lb-radius: 6px;
- --lb-font: 'Inter', system-ui, sans-serif;
+ --lb-shadow: 0 1px 2px rgba(26, 28, 33, 0.06);
+ --lb-font: 'Inter', system-ui, -apple-system, sans-serif;
--lb-mono: 'JetBrains Mono', 'Fira Code', monospace;
}