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

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:
Lucas RayanandClaude Sonnet 5 committed 2026-11-01 08:28:08 -03:00
1 parent c64bbbf775
commit be47b607df
4 files changed
+2176 -142

No files matched your search

+101
View File
@@ -63,4 +63,105 @@ export const EN = {
'Nenhum alerta ainda.': 'No alerts yet.',
'Alertas carregados: {n}': 'Alerts loaded: {n}',
'versão {n}': 'version {n}',
'estado e prioridades': 'status and priorities',
'filtros e evidências': 'filters and evidence',
'fusão por evidência': 'fusion by evidence',
'Hosts': 'Hosts',
'Estado, prioridades e distribuição dos alertas': 'Status, priorities and alert distribution',
'Filtre, ordene e abra a evidência completa': 'Filter, sort and open the full evidence',
'Prioridade por fusão de evidências entre pilares': 'Priority by cross-pillar evidence fusion',
'Ingestão de logs Zeek e relatório da execução': 'Zeek log ingestion and run report',
'carregando…': 'loading…',
'Hosts com alerta': 'Hosts with alerts',
'ranqueados por fusão': 'ranked by fusion',
'melhor host observado': 'top observed host',
'Registros no dataset': 'Dataset records',
'armazenamento local': 'local storage',
'Top hosts por fusão': 'Top hosts by fusion',
'Score ponderado de evidências (0 a 1). Passe o mouse para ver os pilares.': 'Weighted evidence score (0 to 1). Hover to see the pillars.',
'Alertas por pilar': 'Alerts per pillar',
'Distribuição observada. Clique em uma barra para filtrar os alertas.': 'Observed distribution. Click a bar to filter alerts.',
'Alertas prioritários': 'Priority alerts',
'Os alertas com maior score individual. Abra a lista completa para filtrar.': 'The alerts with the highest individual score. Open the full list to filter.',
'Ver todos os alertas': 'View all alerts',
'sem hosts': 'no hosts',
'Filtre, ordene e expanda cada alerta para ver a evidência completa como tabela.': 'Filter, sort and expand each alert to see the full evidence as a table.',
'Ordenar': 'Sort',
'Maior score': 'Highest score',
'Menor score': 'Lowest score',
'Mais recentes': 'Newest',
'Pilar': 'Pillar',
'Todos': 'All',
'Score mínimo': 'Minimum score',
'Buscar': 'Search',
'IP, host, domínio ou ID': 'IP, host, domain or ID',
'Ranking por score de fusão (desc). Clique em uma linha para ver a evidência de cada pilar.': 'Ranked by fusion score (desc). Click a row to see each pillar’s evidence.',
'Buscar host': 'Search host',
'Fusão': 'Fusion',
'Nenhum host carregado.': 'No hosts loaded.',
'Detalhes': 'Details',
'A API de fusão lista apenas hosts com pelo menos um alerta. Hosts sem alerta não aparecem nesta tabela.': 'The fusion API only lists hosts with at least one alert. Hosts without alerts do not appear in this table.',
'Ver alertas': 'View alerts',
'Ver hosts': 'View hosts',
'Estado do serviço': 'Service status',
'Versão, dataset e armazenamento informados pela API local.': 'Version, dataset and storage reported by the local API.',
'{n} pilares ativos': '{n} active pillars',
'{n} com múltiplos pilares': '{n} with multiple pillars',
'{n} partições · {n}': '{n} partitions · {n}',
'{n} de {n} alertas': '{n} of {n} alerts',
'{n} hosts': '{n} hosts',
'Sem evidência registrada.': 'No evidence recorded.',
'Período': 'Period',
'Regularidade': 'Regularity',
'p-valor': 'p-value',
'Significância': 'Significance',
'Observações': 'Observations',
'Sessão': 'Session',
'Partes': 'Parts',
'Falhas': 'Failures',
'Início': 'Start',
'Informação': 'Information',
'Entropia': 'Entropy',
'Únicos': 'Unique',
'Domínio': 'Domain',
'Contribuição': 'Contribution',
'Evidência completa': 'Full evidence',
'Nenhum alerta corresponde aos filtros.': 'No alerts match the filters.',
'Copiar JSON': 'Copy JSON',
'Copiado': 'Copied',
'Ver host': 'View host',
'Sem alerta neste pilar.': 'No alert in this pillar.',
'Abrir alerta': 'Open alert',
'Nenhum host com alerta.': 'No hosts with alerts.',
'Nenhum host corresponde à busca.': 'No hosts match the search.',
'Nenhum host com alerta para exibir.': 'No hosts with alerts to display.',
'Nenhum alerta para exibir.': 'No alerts to display.',
'Pilar {n}: m {n} · peso {n} · contribuição {n}': 'Pillar {n}: m {n} · weight {n} · contribution {n}',
'Pilar {n}: m {n} · contribuição {n}': 'Pillar {n}: m {n} · contribution {n}',
'Score de fusão: {n}': 'Fusion score: {n}',
'Alertas: {n}': 'Alerts: {n}',
'{n}% do total': '{n}% of total',
'Pilar {n}: {n} alertas': 'Pillar {n}: {n} alerts',
'Serviço indisponível.': 'Service unavailable.',
'Versão': 'Version',
'Modo': 'Mode',
'Registros': 'Records',
'Partições': 'Partitions',
'TTL (dias)': 'TTL (days)',
'Backend': 'Backend',
'Status': 'Status',
'Diretório': 'Directory',
'Logs conn': 'conn logs',
'Logs DNS': 'DNS logs',
'Schema de evidência': 'Evidence schema',
'P1 excluídos': 'P1 excluded',
'Análise concluída: {n} registros, {n} alertas e {n} hosts.': 'Analysis complete: {n} records, {n} alerts and {n} hosts.',
'Atualizado às {n}': 'Updated at {n}',
'sim': 'yes',
'não': 'no',
'PCR': 'PCR',
'JSD': 'JSD',
'Hosts por score de fusão': 'Hosts by fusion score',
'Distribuição de alertas por pilar': 'Alert distribution per pillar',
'Filtrar por pilar': 'Filter by pillar',
};