feat(report): add logo and TLP classification badge to the recovery report
CI / Format and clippy (push) Successful in 2m13s
CI / Dependency audit (push) Successful in 12s
CI / Check desktop app (Linux) (push) Successful in 4m21s
CI / Tests (Linux) (push) Successful in 8m23s

The HTML/PDF report had no visual identity (no logo) and no sharing
classification. Embeds the Guará logo as a base64 data URI (so the
standalone export still renders offline, same approach as Sabiá's report)
and adds a TLP:AMBER badge next to the existing "Uso autorizado" one —
forensic recovery findings are sensitive and worth marking for limited
disclosure, same as any other Ciberfauna report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lucas GuerraandClaude Sonnet 5 committed 2026-11-02 11:31:40 -03:00
1 parent 43e9860b41
commit 7683967650
1 file changed
+37 -1
+37 -1
View File
@@ -101,6 +101,37 @@ pub fn csv(found: &[&Found]) -> String {
out
}
/// The Guará logo, embedded so the report renders identically as a
/// standalone, offline HTML/PDF export (same reasoning as
/// `Sabiá/crates/sabia-core/src/report.rs`, which this mirrors): a
/// `src="/logo.png"` reference would 404 once the file is saved outside the
/// app's own server, and a forensic report that only half-renders without
/// network access defeats its purpose as a portable piece of evidence.
const LOGO_PNG: &[u8] = include_bytes!("../../../logo.png");
const B64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/// Encodes the embedded logo as a `data:image/png;base64,...` URI.
fn logo_data_uri() -> String {
let mut out = String::with_capacity(LOGO_PNG.len().div_ceil(3) * 4 + 22);
out.push_str("data:image/png;base64,");
for chunk in LOGO_PNG.chunks(3) {
let b0 = chunk[0];
let b1 = chunk.get(1).copied();
let b2 = chunk.get(2).copied();
out.push(B64[(b0 >> 2) as usize] as char);
out.push(B64[(((b0 & 0b11) << 4) | (b1.unwrap_or(0) >> 4)) as usize] as char);
out.push(match b1 {
Some(b1) => B64[(((b1 & 0b1111) << 2) | (b2.unwrap_or(0) >> 6)) as usize] as char,
None => '=',
});
out.push(match b2 {
Some(b2) => B64[(b2 & 0b111111) as usize] as char,
None => '=',
});
}
out
}
fn esc(s: &str) -> String {
s.replace('&', "&amp;")
.replace('<', "&lt;")
@@ -135,6 +166,9 @@ pub fn html(r: &ReportInput) -> String {
r#"<!doctype html><html lang="pt-BR"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Guará: relatório de recuperação</title><style>
:root{color-scheme:light}*{box-sizing:border-box}body{margin:0;font:14px/1.5 -apple-system,"Segoe UI",Inter,Roboto,sans-serif;color:#10201f;background:#f3f7f7}
main{max-width:1100px;margin:0 auto;padding:36px 28px 60px}header{border-bottom:3px solid #0f766e;padding-bottom:14px;margin-bottom:22px}
.brand{display:flex;align-items:center;gap:10px}.brand img{width:30px;height:30px;object-fit:contain}
.badges{display:flex;gap:6px;margin-top:8px}.badge{font:700 11px ui-monospace,Menlo,Consolas,monospace;letter-spacing:.08em;text-transform:uppercase;padding:2px 8px;border-radius:3px;border:1px solid #0f766e;color:#0f766e;background:#e7f3f2}
.badge-tlp{color:#8a5a00;border-color:#8a5a00;background:#fff0cf}
h1{margin:0;font-size:28px;letter-spacing:-.02em}h2{margin:30px 0 10px;font-size:17px;color:#0f766e}small,.mut{color:#5b6f6d}
table{width:100%;border-collapse:collapse;background:#fff;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.08)}
th,td{padding:7px 10px;text-align:left;border-bottom:1px solid #e4eded;vertical-align:top;font-size:13px}th{background:#e7f3f2;color:#0b4f4a;font-weight:600}
@@ -145,7 +179,9 @@ td.n{text-align:right;font-variant-numeric:tabular-nums}.mono{font-family:ui-mon
footer{margin-top:34px;color:#5b6f6d;font-size:12px}@media print{body{background:#fff}main{padding:0}table{box-shadow:none}h2{break-after:avoid}tr{break-inside:avoid}}
</style></head><body><main>"#,
);
o.push_str("<header><h1>Relatório de recuperação de arquivos</h1><div class=\"mut\">Gerado pelo Guará ");
o.push_str("<header><div class=\"brand\"><img src=\"");
o.push_str(&logo_data_uri());
o.push_str("\" alt=\"\"><h1>Relatório de recuperação de arquivos</h1></div><div class=\"badges\"><span class=\"badge badge-tlp\">TLP:AMBER</span><span class=\"badge\">Uso autorizado</span></div><div class=\"mut\">Gerado pelo Guará ");
o.push_str(env!("CARGO_PKG_VERSION"));
o.push_str(&format!(" em {}</div></header>", fmt_unix(now)));