Interface gráfica: identidade visual própria + redesign

- Logo conceitual em SVG (crates/sabia-cli/src/web/sabia-logo.svg):
  silhueta de pássaro construída por arcos concêntricos em duas cores
  (cobre/âmbar + ciano), reinterpretando os arcos como ondas de sinal
  Wi-Fi — mesma técnica de construção usada no projeto irmão, paleta
  e silhueta próprias.
- Redesign completo da UI local (app.css, app.js, index.html): sidebar
  translúcida com blur, cards, pills, tipografia SF Pro, linha de
  sinal animada só enquanto a captura está ativa, barras de sinal por
  dBm, badges de segurança por cor, dark mode automático. Validado
  visualmente no Chrome headless (claro, escuro, lista de interfaces,
  tabela de resultados) via MCP.
- Novas rotas /app.css, /app.js, /sabia-logo.svg, /api/version,
  /api/update/check no servidor local.
This commit is contained in:
Lucas committed 2026-10-23 08:41:35 -03:00
1 parent 485f628374
commit fd3ef5240e
5 files changed
+453 -138

No files matched your search

+20
View File
@@ -13,6 +13,9 @@ use std::thread;
use std::time::Duration;
const INDEX_HTML: &str = include_str!("index.html");
const APP_CSS: &str = include_str!("app.css");
const APP_JS: &str = include_str!("app.js");
const LOGO_SVG: &str = include_str!("sabia-logo.svg");
struct Session {
table: Arc<Mutex<ScanTable>>,
@@ -107,6 +110,14 @@ fn handle(mut stream: TcpStream, state: &Arc<AppState>, expected_token: &str) ->
return respond(&mut stream, 200, "text/html; charset=utf-8", INDEX_HTML.as_bytes());
}
// Assets estáticos: não carregam dado nenhum, então não exigem token.
match req.path.as_str() {
"/app.css" => return respond(&mut stream, 200, "text/css", APP_CSS.as_bytes()),
"/app.js" => return respond(&mut stream, 200, "application/javascript", APP_JS.as_bytes()),
"/sabia-logo.svg" => return respond(&mut stream, 200, "image/svg+xml", LOGO_SVG.as_bytes()),
_ => {}
}
if !req.path.starts_with("/api/") {
return respond(&mut stream, 404, "text/plain", b"not found");
}
@@ -132,6 +143,15 @@ fn respond(stream: &mut TcpStream, code: u16, ctype: &str, body: &[u8]) -> std::
fn api(state: &Arc<AppState>, req: &Request) -> String {
match (req.method.as_str(), req.path.as_str()) {
("GET", "/api/version") => {
format!("{{\"version\":{:?}}}", env!("CARGO_PKG_VERSION"))
}
("GET", "/api/update/check") => {
match sabia_core::update::check(env!("CARGO_PKG_VERSION")) {
Ok(status) => serde_json::to_string(&status).unwrap_or_else(|_| "{}".into()),
Err(e) => format!("{{\"error\":{:?}}}", e.to_string()),
}
}
("GET", "/api/ifaces") => {
let list = iface::list_wireless().unwrap_or_default();
serde_json::to_string(&list).unwrap_or_else(|_| "[]".into())