chore(i18n): finish translating code comments and scripts to English
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s
Completes the English-only standardization for code, comments, scripts and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en, untouched). Covers the remaining crates/, apps/gui/ui/ and apps/gui/src-tauri/ comments, .gitea/ workflow and template comments, .githooks/ hooks, and the scripts/ source comments for the files already renamed in a prior commit (check.sh, generate-logo.py, install-hooks.sh). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
This commit is contained in:
1 parent
bf059fd0bc
commit
55e19a8e5b
59 files changed
+935
-879
No files matched your search
+65
-67
@@ -53,11 +53,11 @@ function applyTheme(t) {
|
||||
|
||||
function updateThemePref(t) {
|
||||
applyTheme(t);
|
||||
try { localStorage.setItem('sabia-theme', t); } catch { /* sem armazenamento */ }
|
||||
try { localStorage.setItem('sabia-theme', t); } catch { /* storage unavailable */ }
|
||||
}
|
||||
|
||||
let themePref = 'auto';
|
||||
try { themePref = localStorage.getItem('sabia-theme') || 'auto'; } catch { /* sem armazenamento */ }
|
||||
try { themePref = localStorage.getItem('sabia-theme') || 'auto'; } catch { /* storage unavailable */ }
|
||||
applyTheme(themePref);
|
||||
|
||||
document.querySelectorAll('[data-theme-pick]').forEach((b) => {
|
||||
@@ -67,7 +67,7 @@ document.querySelectorAll('[data-theme-pick]').forEach((b) => {
|
||||
try {
|
||||
const sort = localStorage.getItem('sabia-sort');
|
||||
if (sort && [...els.sortSel.options].some((o) => o.value === sort)) els.sortSel.value = sort;
|
||||
} catch { /* sem armazenamento */ }
|
||||
} catch { /* storage unavailable */ }
|
||||
|
||||
els.settingsBtn.addEventListener('click', () => {
|
||||
if (!els.settingsDlg.open) els.settingsDlg.showModal();
|
||||
@@ -75,14 +75,14 @@ els.settingsBtn.addEventListener('click', () => {
|
||||
|
||||
function applyLangPref() {
|
||||
let pref = 'auto';
|
||||
try { pref = localStorage.getItem('sabia-lang') || 'auto'; } catch { /* ignora */ }
|
||||
try { pref = localStorage.getItem('sabia-lang') || 'auto'; } catch { /* ignore */ }
|
||||
els.langSel.value = pref;
|
||||
setLang(pref === 'auto' ? undefined : pref);
|
||||
}
|
||||
|
||||
els.langSel.addEventListener('change', () => {
|
||||
const pref = els.langSel.value;
|
||||
try { localStorage.setItem('sabia-lang', pref); } catch { /* ignora */ }
|
||||
try { localStorage.setItem('sabia-lang', pref); } catch { /* ignore */ }
|
||||
setLang(pref === 'auto' ? undefined : pref);
|
||||
});
|
||||
|
||||
@@ -90,12 +90,12 @@ let ifaces = [];
|
||||
let current = null;
|
||||
let scanning = false;
|
||||
|
||||
// Ordem de chegada estável por rede/BSSID: sem isso, cada poll reordenava a
|
||||
// lista pelo sinal instantâneo e as linhas ficavam "flutuando" de posição a
|
||||
// cada atualização, tornando impossível clicar no alvo certo. A ordem de
|
||||
// chegada serve de critério de desempate (e de ordenação padrão), então um
|
||||
// item só muda de posição se o usuário trocar o modo de ordenação de
|
||||
// verdade — não por uma flutuação de -1 dBm entre um poll e outro.
|
||||
// Stable arrival order per network/BSSID: without this, each poll resorted the
|
||||
// list by instantaneous RSSI and rows jumped around on every
|
||||
// update, making it hard to click a target. Arrival order
|
||||
// serves as the tiebreaker (and default sort), so an
|
||||
// item only moves when the user explicitly changes the sort mode
|
||||
// rather than on a 1 dBm RSSI fluctuation between polls.
|
||||
const firstSeenOrder = new Map();
|
||||
let firstSeenSeq = 0;
|
||||
function orderOf(key) {
|
||||
@@ -115,8 +115,8 @@ async function api(path, opts) {
|
||||
return r.json();
|
||||
}
|
||||
|
||||
// No app desktop não existe servidor HTTP: as mesmas rotas viram chamadas
|
||||
// IPC diretas pro núcleo, pelos comandos registrados em src-tauri/src/main.rs.
|
||||
// In the desktop app there is no HTTP server: the same routes map to direct
|
||||
// IPC calls into the core via the Tauri commands in src-tauri/src/main.rs.
|
||||
async function tauriApi(path, opts = {}) {
|
||||
const [p, qs] = path.split('?');
|
||||
const q = new URLSearchParams(qs || '');
|
||||
@@ -280,8 +280,8 @@ function renderEmptyResult(message, hint) {
|
||||
els.resultArea.innerHTML = `<div class="ap-card empty-state">${ICON_EMPTY}<h2>${message}</h2><p>${hint}</p></div>`;
|
||||
}
|
||||
|
||||
// Só troca o HTML quando muda de verdade: o poll de 1 s não deve
|
||||
// reconstruir a tabela (e perder foco/hover) à toa.
|
||||
// Only update innerHTML when content actually changes: the 1s poll must not
|
||||
// rebuild the DOM table (and drop focus/hover) unnecessarily.
|
||||
function setResultsHtml(html) {
|
||||
if (els.resultArea._h === html) return;
|
||||
els.resultArea._h = html;
|
||||
@@ -352,9 +352,9 @@ els.btnScan.addEventListener('click', async () => {
|
||||
if (!scanning) {
|
||||
const res = await api(`/api/scan/start?iface=${encodeURIComponent(current)}`, { method: 'POST' });
|
||||
if (res && res.ok === false) {
|
||||
// Sem isso, uma falha ao abrir a captura (permissão, interface saiu do
|
||||
// modo monitor, etc.) ficava muda: o botão virava "parar captura" e a
|
||||
// tela mostrava "escutando o ar" pra sempre, sem nenhuma rede chegar.
|
||||
// Without this, a failure to start capture (permissions, interface left
|
||||
// monitor mode, etc.) was silent: the button flipped to "stop capture" and
|
||||
// the screen showed "listening" forever without any networks arriving.
|
||||
alert('Não foi possível iniciar a captura: ' + (res.error || 'erro desconhecido'));
|
||||
return;
|
||||
}
|
||||
@@ -398,7 +398,7 @@ function signalOf(ap) {
|
||||
}
|
||||
|
||||
function signalBars(dbm) {
|
||||
// dBm tipicamente entre -30 (ótimo) e -90 (péssimo)
|
||||
// dBm typically ranges from -30 (excellent) to -90 (weak)
|
||||
let level = 0;
|
||||
if (dbm != null) {
|
||||
if (dbm >= -55) level = 4;
|
||||
@@ -414,9 +414,9 @@ function signalBars(dbm) {
|
||||
return `<span class="signal-bars">${bars}</span>`;
|
||||
}
|
||||
|
||||
// Agrupa por ESSID: uma rede pode ter vários BSSIDs (rádios) e todos
|
||||
// pertencem ao mesmo alvo. Redes ocultas ficam separadas, pois sem SSID
|
||||
// não há como afirmar que dois BSSIDs são a mesma rede.
|
||||
// Group by ESSID: a network may have multiple BSSIDs (radios) belonging
|
||||
// to the same target. Hidden networks stay separate since without an SSID
|
||||
// two BSSIDs cannot be assumed to belong to the same network.
|
||||
function buildGroups(aps) {
|
||||
const map = new Map();
|
||||
for (const ap of aps) {
|
||||
@@ -460,9 +460,9 @@ function filterGroups(groups) {
|
||||
g.aps.some((a) => a.bssid.toLowerCase().includes(q)));
|
||||
}
|
||||
|
||||
// Dessensibiliza o sinal antes de comparar: arredondar pro m\u00faltiplo de 4
|
||||
// dBm mais pr\u00f3ximo evita que uma oscila\u00e7\u00e3o de 1-2 dBm entre dois polls
|
||||
// inverta a ordem de dois alvos com sinal parecido a cada atualiza\u00e7\u00e3o.
|
||||
// Quantize RSSI before comparing: rounding to the nearest multiple of 4
|
||||
// dBm prevents a 1-2 dBm fluctuation between polls from
|
||||
// flipping the order of two targets with similar signal strength.
|
||||
function signalBucket(v) {
|
||||
return v == null ? -1000 : Math.round(v / 4) * 4;
|
||||
}
|
||||
@@ -535,7 +535,7 @@ function renderGroup(g) {
|
||||
let lastAps = [];
|
||||
const openGroups = new Set();
|
||||
let viewMode = 'nets';
|
||||
try { viewMode = localStorage.getItem('sabia-view') || 'nets'; } catch { /* sem armazenamento */ }
|
||||
try { viewMode = localStorage.getItem('sabia-view') || 'nets'; } catch { /* storage unavailable */ }
|
||||
let channelBand = null;
|
||||
let reportState = { iface: null, pages: 1, zoom: 1, url: null };
|
||||
let reportMeasureTimer = null;
|
||||
@@ -632,8 +632,8 @@ function spectrumSvg(band) {
|
||||
|
||||
const CHAN_TONE_WORD = { ok: 'Boa', warn: 'Atenção', bad: 'Saturado' };
|
||||
|
||||
/** Estado geral da banda: mesma lógica de "saúde" usada nos tons por canal,
|
||||
* só que aplicada ao canal recomendado — é o que decide o tom do card inteiro. */
|
||||
/** Overall band status: applies the per-channel health logic to the
|
||||
* recommended channel to determine the summary card tone. */
|
||||
function bandHealth(band, rec) {
|
||||
if (!rec) return { tone: 'bad', label: 'Sem canal livre nessa banda' };
|
||||
if (rec.score >= 1.5) return { tone: 'bad', label: 'Até o melhor canal está saturado' };
|
||||
@@ -752,7 +752,7 @@ function renderResults(aps) {
|
||||
|
||||
function setView(mode) {
|
||||
viewMode = mode === 'channels' ? 'channels' : 'nets';
|
||||
try { localStorage.setItem('sabia-view', viewMode); } catch { /* ignora */ }
|
||||
try { localStorage.setItem('sabia-view', viewMode); } catch { /* ignore */ }
|
||||
document.querySelectorAll('#viewNav [data-view]').forEach((b) => {
|
||||
b.setAttribute('aria-pressed', String(b.dataset.view === viewMode));
|
||||
});
|
||||
@@ -779,7 +779,7 @@ els.resultArea.addEventListener('click', (e) => {
|
||||
|
||||
els.searchInput.addEventListener('input', () => renderResults(lastAps));
|
||||
els.sortSel.addEventListener('change', () => {
|
||||
try { localStorage.setItem('sabia-sort', els.sortSel.value); } catch { /* sem armazenamento */ }
|
||||
try { localStorage.setItem('sabia-sort', els.sortSel.value); } catch { /* storage unavailable */ }
|
||||
renderResults(lastAps);
|
||||
});
|
||||
els.resultArea.addEventListener('click', (e) => {
|
||||
@@ -803,7 +803,7 @@ els.resultArea.addEventListener('keydown', (e) => {
|
||||
openTarget(target.dataset.bssid);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------- alvo
|
||||
// ---------------------------------------------------------------- target
|
||||
let lastSnapshot = null;
|
||||
let selectedBssid = null;
|
||||
let activeTab = 'overview';
|
||||
@@ -1056,8 +1056,8 @@ const EVENT_LABEL = {
|
||||
deauth: 'desautenticado',
|
||||
};
|
||||
|
||||
/** Sparkline em tamanho real (não o mini de 62px da tabela), pra caber bem
|
||||
* sozinho dentro do card de sinal do detalhe do cliente. */
|
||||
/** Full-size RSSI sparkline (wider than the 62px table mini-chart) for the
|
||||
* client detail signal card. */
|
||||
function bigSparkline(history) {
|
||||
const points = (history || []).slice(-60);
|
||||
if (points.length < 2) return '<p class="hint">Sem amostras de sinal suficientes ainda.</p>';
|
||||
@@ -1077,9 +1077,8 @@ function bigSparkline(history) {
|
||||
return `<svg class="spark spark-big" viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" aria-hidden="true"><path d="${d}"/></svg>`;
|
||||
}
|
||||
|
||||
/** Visão completa de um cliente: tudo que o Sabiá já capturou sobre ele —
|
||||
* identificação, rede, sinal e a linha do tempo de eventos — num painel só,
|
||||
* em vez de ter que adivinhar olhando a linha compacta da tabela. */
|
||||
/** Detailed client view: consolidates everything captured about a station —
|
||||
* identity hints, associated AP, signal history, and event timeline — in one modal. */
|
||||
function renderClientDetail(clients) {
|
||||
const client = clients.find((c) => c.mac === attack.timelineMac);
|
||||
if (!client) return '';
|
||||
@@ -1522,11 +1521,11 @@ async function loadSnapshot() {
|
||||
}
|
||||
}
|
||||
|
||||
// Enquanto startDeauthJob está religando a captura e esperando o AP
|
||||
// reaparecer na varredura, a sessão nova responde vazia por alguns ciclos
|
||||
// de poll — sem essa trava, renderTarget() interpreta "AP não visto ainda"
|
||||
// como "AP não existe mais" e fecha o diálogo debaixo do usuário no meio
|
||||
// da própria ação que ele acabou de pedir.
|
||||
// While startDeauthJob is restarting capture and waiting for the AP
|
||||
// to reappear in the scan, the fresh session returns empty for a few poll
|
||||
// cycles — without this guard, renderTarget() treats "AP not seen yet"
|
||||
// as "AP gone" and closes the modal underneath the user mid-action.
|
||||
//
|
||||
let resumingForBssid = null;
|
||||
|
||||
function renderTarget(force) {
|
||||
@@ -1671,13 +1670,13 @@ async function waitForBssidChannel(bssid, timeoutMs) {
|
||||
}
|
||||
|
||||
async function startDeauthJob(handshake) {
|
||||
// Fixa o alvo no início: o resto desta função faz esperas longas
|
||||
// (religar a captura, aguardar a rede reaparecer na varredura), e nesse
|
||||
// meio tempo o poll global pode não achar mais o AP na primeira resposta
|
||||
// vazia da sessão recém-reiniciada e fechar o diálogo, zerando
|
||||
// `selectedBssid`. Sem essa cópia local, o job acabava sendo disparado
|
||||
// com bssid=null ("MAC inválido: null") mesmo depois de localizar a rede
|
||||
// com sucesso.
|
||||
// Snapshot the target BSSID upfront: the rest of this function awaits
|
||||
// capture restart and AP rediscovery, during which the global poll could
|
||||
// see an empty initial snapshot and close the modal, clearing
|
||||
// `selectedBssid`.
|
||||
//
|
||||
//
|
||||
//
|
||||
const bssid = selectedBssid;
|
||||
const station = document.getElementById('deauthStation')?.value || '';
|
||||
const direction = document.getElementById('deauthDirection')?.value || attack.direction;
|
||||
@@ -1688,10 +1687,10 @@ async function startDeauthJob(handshake) {
|
||||
setResult('deauthResult', 'Escolha um cliente para desautenticar nas direções sta/both.', true);
|
||||
return;
|
||||
}
|
||||
// Essa ação exige uma sessão de captura ativa (é o que guarda o handshake
|
||||
// montado); sem isso o backend recusa com "inicie a captura antes do
|
||||
// job". Em vez de obrigar o usuário a voltar e religar manualmente, retoma
|
||||
// a captura aqui — do jeito que ele descreveu querer.
|
||||
// This action requires an active capture session (which stores the assembled
|
||||
// handshake); rather than forcing the user to manually restart capture,
|
||||
// resume capture automatically here.
|
||||
//
|
||||
if (!scanning) {
|
||||
const scanRes = await api(`/api/scan/start?iface=${encodeURIComponent(current)}`, { method: 'POST' });
|
||||
if (scanRes && scanRes.ok === false) {
|
||||
@@ -1702,12 +1701,12 @@ async function startDeauthJob(handshake) {
|
||||
els.btnScan.textContent = 'parar captura';
|
||||
els.btnScan.classList.add('live');
|
||||
els.wave.classList.add('scanning');
|
||||
// A captura acabou de reiniciar: a sessão ainda não sabe em qual canal
|
||||
// este BSSID está, e o job travaria o canal errado (o canal em que o
|
||||
// scanner por acaso já estava saltando) em vez do canal real do AP —
|
||||
// os quadros de deauth saem no vácuo e o job termina sem efeito. Espera
|
||||
// o AP reaparecer no snapshot (ele já esteve no ar, é questão de
|
||||
// segundos de varredura) antes de disparar o job.
|
||||
// Capture just restarted: the session does not yet know which channel
|
||||
// this BSSID is on, so starting the job immediately would lock the wrong
|
||||
// channel. Wait briefly for the AP to reappear in the scan snapshot
|
||||
// before launching the job.
|
||||
//
|
||||
//
|
||||
setResult('deauthResult', 'Retomando a captura e localizando a rede…');
|
||||
resumingForBssid = bssid;
|
||||
let found;
|
||||
@@ -2370,10 +2369,9 @@ async function poll() {
|
||||
if (scanning && current) {
|
||||
const snap = await api(`/api/scan/snapshot?iface=${encodeURIComponent(current)}`);
|
||||
if (snap?.access_points && snap.running === false) {
|
||||
// A sessão existe e respondeu normal (200), mas o próprio engine
|
||||
// marcou running=false: a thread de captura morreu sozinha (erro de
|
||||
// I/O, adaptador caiu da USB, etc.). Sem isso, a UI continuava achando
|
||||
// que ainda estava capturando e nunca contava o motivo real.
|
||||
// The session exists and returned HTTP 200, but the engine marked
|
||||
// running=false because the capture thread died on its own (I/O error,
|
||||
// USB adapter unplugged, etc.). Surface the actual reason to the UI.
|
||||
scanning = false;
|
||||
els.btnScan.textContent = 'iniciar captura';
|
||||
els.btnScan.classList.remove('live');
|
||||
@@ -2386,10 +2384,10 @@ async function poll() {
|
||||
renderResults(snap.access_points);
|
||||
if (selectedBssid) renderTarget(false);
|
||||
} else if (snap && snap.ok === false) {
|
||||
// A captura morreu no meio (interface saiu do monitor, processo externo
|
||||
// tomou o adaptador, etc.). Antes disso caía no fallback de baixo, que
|
||||
// sempre responde 200 com lista vazia — a UI continuava "escutando o
|
||||
// ar" indefinidamente sem nenhuma rede aparecer e sem avisar por quê.
|
||||
// Capture stopped mid-flight (interface left monitor mode, external
|
||||
// process claimed the adapter, etc.). Without this branch, the fallback
|
||||
// below returns 200 with an empty list and the UI keeps listening
|
||||
// indefinitely without any network appearing or explaining why.
|
||||
scanning = false;
|
||||
els.btnScan.textContent = 'iniciar captura';
|
||||
els.btnScan.classList.remove('live');
|
||||
@@ -2405,7 +2403,7 @@ async function poll() {
|
||||
}
|
||||
|
||||
async function checkElevation() {
|
||||
if (!tauri) return; // via `sabia ui` a elevação é responsabilidade do terminal (sudo)
|
||||
if (!tauri) return; // in `sabia ui` mode privilege elevation is handled by the terminal (sudo)
|
||||
const notice = document.getElementById('elevateNotice');
|
||||
const msg = document.getElementById('elevateMsg');
|
||||
const retry = document.getElementById('btnElevateRetry');
|
||||
|
||||
Reference in new issue
Block a user