feat: build a real dashboard and fix dead panel navigation in the web UI
Release / Plan (reuse the published image?) (push) Successful in 4s
CI / Docker image (build and smoke test) (push) Skipped
CI / Format and clippy (push) Successful in 16s
CI / Dependency audit (push) Successful in 17s
CI / Tests (Linux) (push) Successful in 34s
Release / ci (push) Successful in 35s
Release / Build image (arm64) (push) Successful in 4m55s
Release / Build image (amd64) (push) Failing after 11m10s
Release / Publish tags (push) Skipped
Release / Gitea release (push) Skipped
Release / Plan (reuse the published image?) (push) Successful in 4s
CI / Docker image (build and smoke test) (push) Skipped
CI / Format and clippy (push) Successful in 16s
CI / Dependency audit (push) Successful in 17s
CI / Tests (Linux) (push) Successful in 34s
Release / ci (push) Successful in 35s
Release / Build image (arm64) (push) Successful in 4m55s
Release / Build image (amd64) (push) Failing after 11m10s
Release / Publish tags (push) Skipped
Release / Gitea release (push) Skipped
The sidebar nav (Overview/Alerts/Analysis) only did scrollIntoView on a
single stacked page, so clicking it produced no visible change. The
Alerts/Analysis panels were also placeholder shells with no real data
exploration despite the API already exposing rich per-pillar evidence.
- app.js/app.css/index.html: real panel switching via showView()/currentView,
toggling an .active class with `.view:not(.active) { display: none }`;
settings keeps opening its own dialog without disturbing the active view.
- New Hosts panel: table of observed hosts ranked by fusion score, with
per-pillar (P1/P2/P3) columns, search, and drill-down into each pillar's
evidence. Documents the known limitation that /api/alerts only lists hosts
with at least one alert (no /api/hosts endpoint exists yet), so a benign
host with no alert won't appear.
- Alerts panel: filterable by pillar and minimum score, sortable, with
full evidence rendered as a formatted key/value table instead of raw JSON.
- Overview panel: KPI tiles (alert count, hosts with alerts, max score,
dataset records) plus two interactive SVG charts (top hosts by fusion
score, alert distribution by pillar) with hover tooltips.
- i18n-en.js: English strings for all new UI text.
No Rust files changed; this is purely apps/gui/ui/ consuming the existing
/api/alerts, /api/analyze and /api/status endpoints.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
1 parent
c64bbbf775
commit
be47b607df
4 files changed
+2176
-142
No files matched your search
+233
-66
@@ -20,13 +20,17 @@
|
||||
|
||||
<div class="side-title">Navegação</div>
|
||||
<nav class="nav" aria-label="Navegação principal">
|
||||
<button type="button" class="nav-item active" data-target="overview">
|
||||
<button type="button" class="nav-item active" data-target="overview" aria-current="page">
|
||||
<span class="nav-icon">◈</span>
|
||||
<span><span class="nav-label">Visão geral</span><span class="nav-sub">painel e estado</span></span>
|
||||
<span><span class="nav-label">Visão geral</span><span class="nav-sub">estado e prioridades</span></span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" data-target="alerts">
|
||||
<span class="nav-icon">◎</span>
|
||||
<span><span class="nav-label">Alertas</span><span class="nav-sub">evidências recentes</span></span>
|
||||
<span><span class="nav-label">Alertas</span><span class="nav-sub">filtros e evidências</span></span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" data-target="hosts">
|
||||
<span class="nav-icon">▤</span>
|
||||
<span><span class="nav-label">Hosts</span><span class="nav-sub">fusão por evidência</span></span>
|
||||
</button>
|
||||
<button type="button" class="nav-item" data-target="analysis">
|
||||
<span class="nav-icon">⌁</span>
|
||||
@@ -44,87 +48,250 @@
|
||||
<span class="pill muted">pré-lançamento</span>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<span id="version" class="mono" data-i18n-skip>—</span>
|
||||
<span id="version" class="mono">—</span>
|
||||
<button id="updateBtn" type="button" class="link-btn">checar atualização</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="main">
|
||||
<header class="topbar" id="overview">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Acauã</p>
|
||||
<h1>Caça a ameaças em rede</h1>
|
||||
<p class="sub">Pesquisa determinística, sem IA opaca</p>
|
||||
<h1 id="viewTitle">Visão geral</h1>
|
||||
<p id="viewSub" class="sub">Estado, prioridades e distribuição dos alertas</p>
|
||||
</div>
|
||||
<div class="top-actions">
|
||||
<span id="lastUpdated" class="mono last-updated">—</span>
|
||||
<button id="refreshBtn" type="button" class="btn">Atualizar status</button>
|
||||
<button id="settingsBtn" type="button" class="icon-btn" aria-label="Configurações" title="Configurações">⚙</button>
|
||||
</div>
|
||||
<button id="settingsBtn" type="button" class="icon-btn" aria-label="Configurações" title="Configurações">⚙</button>
|
||||
</header>
|
||||
|
||||
<section class="notice">
|
||||
<strong>Estado atual.</strong>
|
||||
<span>Ingestão de conn.log e dns.log do Zeek, armazenamento Parquet/DuckDB e os pilares 1 a 4 em versão inicial, validados com tráfego público real. Ainda não: ingestão contínua automática, alertas persistidos entre reinícios, movimentação lateral e sessões UDP.</span>
|
||||
</section>
|
||||
<section class="view active" id="overview">
|
||||
<div class="kpi-grid">
|
||||
<article class="kpi">
|
||||
<span class="kpi-label">Alertas</span>
|
||||
<strong id="kpiAlerts" class="kpi-value">—</strong>
|
||||
<span id="kpiAlertsSub" class="kpi-sub">carregando…</span>
|
||||
</article>
|
||||
<article class="kpi">
|
||||
<span class="kpi-label">Hosts com alerta</span>
|
||||
<strong id="kpiHosts" class="kpi-value">—</strong>
|
||||
<span id="kpiHostsSub" class="kpi-sub">ranqueados por fusão</span>
|
||||
</article>
|
||||
<article class="kpi">
|
||||
<span class="kpi-label">Maior score</span>
|
||||
<strong id="kpiScore" class="kpi-value">—</strong>
|
||||
<span id="kpiScoreSub" class="kpi-sub">melhor host observado</span>
|
||||
</article>
|
||||
<article class="kpi">
|
||||
<span class="kpi-label">Registros no dataset</span>
|
||||
<strong id="kpiRecords" class="kpi-value">—</strong>
|
||||
<span id="kpiRecordsSub" class="kpi-sub">armazenamento local</span>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<section class="notice">
|
||||
<strong>Uso autorizado.</strong>
|
||||
<span>Use somente em redes próprias ou com autorização explícita. Nenhum pacote é enviado para fora.</span>
|
||||
</section>
|
||||
<div class="charts-grid">
|
||||
<section class="panel chart-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Top hosts por fusão</h2>
|
||||
<p class="hint">Score ponderado de evidências (0 a 1). Passe o mouse para ver os pilares.</p>
|
||||
</div>
|
||||
<span class="panel-tag">m1 · m2 · m3</span>
|
||||
</div>
|
||||
<div class="chart-wrap">
|
||||
<svg id="hostChart" class="chart" role="img" aria-label="Hosts por score de fusão"></svg>
|
||||
<div id="hostTip" class="chart-tip" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pillars" aria-label="Pilares de detecção">
|
||||
<article class="card">
|
||||
<span class="card-kicker">Pilar 1</span>
|
||||
<h2>Beaconing</h2>
|
||||
<p>Periodograma de Rayleigh/Schuster com grade limitada ao intervalo observado.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card">
|
||||
<span class="card-kicker">Pilar 2</span>
|
||||
<h2>Sessões persistentes</h2>
|
||||
<p>Sessões TCP longas, inclusive as que o Zeek divide por inatividade.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card">
|
||||
<span class="card-kicker">Pilar 3</span>
|
||||
<h2>Anomalias de protocolo</h2>
|
||||
<p>Volume de informação (entropia de Shannon) em subdomínios DNS.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card">
|
||||
<span class="card-kicker">Pilar 4</span>
|
||||
<h2>Fusão de evidências</h2>
|
||||
<p>Prioridade por host com evidência por alerta.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
</section>
|
||||
<section class="panel chart-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Alertas por pilar</h2>
|
||||
<p class="hint">Distribuição observada. Clique em uma barra para filtrar os alertas.</p>
|
||||
</div>
|
||||
<span class="panel-tag">P1 · P2 · P3</span>
|
||||
</div>
|
||||
<div class="chart-wrap">
|
||||
<svg id="pillarChart" class="chart" role="img" aria-label="Distribuição de alertas por pilar"></svg>
|
||||
<div id="pillarTip" class="chart-tip" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="panel" id="analysis">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Analisar dataset</h2>
|
||||
<p class="hint">Aponte para um diretório de logs Zeek ou para um dataset local.</p>
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Alertas prioritários</h2>
|
||||
<p class="hint">Os alertas com maior score individual. Abra a lista completa para filtrar.</p>
|
||||
</div>
|
||||
<button id="seeAllAlerts" type="button" class="btn">Ver todos os alertas</button>
|
||||
</div>
|
||||
<button id="refreshBtn" type="button" class="btn">Atualizar status</button>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label class="field">
|
||||
<span>Diretório de logs</span>
|
||||
<input id="datasetPath" type="text" placeholder="/var/log/zeek/current" autocomplete="off" spellcheck="false">
|
||||
</label>
|
||||
<button id="analyzeBtn" type="button" class="btn primary">Analisar</button>
|
||||
</div>
|
||||
<pre id="output" class="output">Nenhuma análise executada.</pre>
|
||||
<div id="overviewAlerts" class="overview-alerts"></div>
|
||||
</section>
|
||||
|
||||
<section class="pillars" aria-label="Pilares de detecção">
|
||||
<article class="card" data-pillar-card="1" tabindex="0" role="button">
|
||||
<span class="card-kicker">Pilar 1</span>
|
||||
<h2>Beaconing <span class="card-count" data-pillar-count="1">0</span></h2>
|
||||
<p>Periodograma de Rayleigh/Schuster com grade limitada ao intervalo observado.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card" data-pillar-card="2" tabindex="0" role="button">
|
||||
<span class="card-kicker">Pilar 2</span>
|
||||
<h2>Sessões persistentes <span class="card-count" data-pillar-count="2">0</span></h2>
|
||||
<p>Sessões TCP longas, inclusive as que o Zeek divide por inatividade.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card" data-pillar-card="3" tabindex="0" role="button">
|
||||
<span class="card-kicker">Pilar 3</span>
|
||||
<h2>Anomalias de protocolo <span class="card-count" data-pillar-count="3">0</span></h2>
|
||||
<p>Volume de informação (entropia de Shannon) em subdomínios DNS.</p>
|
||||
<span class="tag">versão inicial</span>
|
||||
</article>
|
||||
<article class="card" data-pillar-card="4" tabindex="0" role="button">
|
||||
<span class="card-kicker">Pilar 4</span>
|
||||
<h2>Fusão de evidências</h2>
|
||||
<p>Prioridade por host com evidência por alerta.</p>
|
||||
<span class="tag" id="pillarFusionTag">sem hosts</span>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="notice">
|
||||
<strong>Uso autorizado.</strong>
|
||||
<span>Use somente em redes próprias ou com autorização explícita. Nenhum pacote é enviado para fora.</span>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="panel" id="alerts">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Alertas recentes</h2>
|
||||
<p class="hint">Cada alerta carrega a evidência determinística que o sustenta.</p>
|
||||
<section class="view" id="alerts" hidden>
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Alertas</h2>
|
||||
<p class="hint">Filtre, ordene e expanda cada alerta para ver a evidência completa como tabela.</p>
|
||||
</div>
|
||||
<span id="alertCount" class="panel-tag">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="alertList" class="alert-list">
|
||||
<li class="empty">Nenhum alerta ainda. Analise um diretório de logs do Zeek.</li>
|
||||
</ul>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="chips" id="pillarFilters" role="group" aria-label="Filtrar por pilar">
|
||||
<button type="button" class="chip active" data-pillar="all">Todos</button>
|
||||
<button type="button" class="chip" data-pillar="1">Pilar 1</button>
|
||||
<button type="button" class="chip" data-pillar="2">Pilar 2</button>
|
||||
<button type="button" class="chip" data-pillar="3">Pilar 3</button>
|
||||
</div>
|
||||
<label class="field compact">
|
||||
<span>Ordenar</span>
|
||||
<select id="alertSort">
|
||||
<option value="score-desc">Maior score</option>
|
||||
<option value="score-asc">Menor score</option>
|
||||
<option value="pillar">Pilar</option>
|
||||
<option value="newest">Mais recentes</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field compact range-field">
|
||||
<span>Score mínimo <output id="minScoreOut">0,00</output></span>
|
||||
<input id="alertMin" type="range" min="0" max="1" step="0.01" value="0">
|
||||
</label>
|
||||
<label class="field compact search-field">
|
||||
<span>Buscar</span>
|
||||
<input id="alertSearch" type="search" placeholder="IP, host, domínio ou ID" autocomplete="off">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="alertList" class="alert-cards">
|
||||
<p class="empty">Nenhum alerta ainda. Analise um diretório de logs do Zeek.</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="view" id="hosts" hidden>
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Hosts observados</h2>
|
||||
<p class="hint">Ranking por score de fusão (desc). Clique em uma linha para ver a evidência de cada pilar.</p>
|
||||
</div>
|
||||
<span id="hostCount" class="panel-tag">—</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar toolbar-slim">
|
||||
<label class="field compact search-field">
|
||||
<span>Buscar host</span>
|
||||
<input id="hostSearch" type="search" placeholder="192.168.1.50" autocomplete="off" spellcheck="false">
|
||||
</label>
|
||||
<div class="legend">
|
||||
<span class="legend-item"><i class="swatch p1"></i>Pilar 1</span>
|
||||
<span class="legend-item"><i class="swatch p2"></i>Pilar 2</span>
|
||||
<span class="legend-item"><i class="swatch p3"></i>Pilar 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table class="host-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">Host</th>
|
||||
<th scope="col">Fusão</th>
|
||||
<th scope="col">P1</th>
|
||||
<th scope="col">P2</th>
|
||||
<th scope="col">P3</th>
|
||||
<th scope="col">Alertas</th>
|
||||
<th scope="col"><span class="sr-only">Detalhes</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="hostRows">
|
||||
<tr><td colspan="8" class="empty">Nenhum host carregado.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="footnote">A API de fusão lista apenas hosts com pelo menos um alerta. Hosts sem alerta não aparecem nesta tabela.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="view" id="analysis" hidden>
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Analisar dataset</h2>
|
||||
<p class="hint">Aponte para um diretório de logs Zeek ou para um dataset local.</p>
|
||||
</div>
|
||||
<span id="reportStatus" class="panel-tag">—</span>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<label class="field">
|
||||
<span>Diretório de logs</span>
|
||||
<input id="datasetPath" type="text" placeholder="/var/log/zeek/current" autocomplete="off" spellcheck="false">
|
||||
</label>
|
||||
<button id="analyzeBtn" type="button" class="btn primary">Analisar</button>
|
||||
</div>
|
||||
|
||||
<div id="reportSummary" class="summary-grid" hidden></div>
|
||||
|
||||
<div class="report-detail">
|
||||
<pre id="output" class="output">Nenhuma análise executada.</pre>
|
||||
<div class="report-actions">
|
||||
<button id="gotoAlerts" type="button" class="btn" hidden>Ver alertas</button>
|
||||
<button id="gotoHosts" type="button" class="btn" hidden>Ver hosts</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Estado do serviço</h2>
|
||||
<p class="hint">Versão, dataset e armazenamento informados pela API local.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="serviceSummary" class="summary-grid summary-grid-wide"></div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<footer class="foot">
|
||||
|
||||
Reference in new issue
Block a user