mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-01 17:17:14 +00:00
Compare commits
4 Commits
79c6166181
...
47eae4a774
| Author | SHA1 | Date | |
|---|---|---|---|
| 47eae4a774 | |||
| db5d5dbcf1 | |||
| 41dbdadaa2 | |||
| e1b2e6ccbb |
|
|
@ -1,7 +1,8 @@
|
|||
{# SPDX-License-Identifier: LicenseRef-CMSD-1.0 #}
|
||||
{# Public landing page — kbin.gk2.secubox.in #}
|
||||
{# Radical-simplify redesign (#543): animated hero + one CTA + install panel
|
||||
up top ; everything else folded behind "En savoir plus". #}
|
||||
{# #683 restyle: aligned with the new /report look — system font, rounded
|
||||
--panel/--line cards, cleaner accents. Dynamic bits (data-live KPIs + JS,
|
||||
install panels, cert-probe, ?mh links) unchanged. #}
|
||||
<!DOCTYPE html>
|
||||
<html lang=fr><head>
|
||||
<meta charset=UTF-8>
|
||||
|
|
@ -10,108 +11,84 @@
|
|||
<title>👁️ VILLAGE3B — Qui te piste ?</title>
|
||||
<link rel=manifest href=/manifest.json>
|
||||
<style>
|
||||
:root{--bg:#0a0a0f;--bg2:#0e0e15;--phos:#00dd44;--phos-hot:#00ff55;--dim:#006622;--text:#e8e6d9;--purple:#9e76ff;--gold:#c9a84c;--amber:#ffb347;--red:#ff4466;--cyan:#00d4ff}
|
||||
:root{--bg:#0a0a0f;--panel:#11131a;--soft:#0d0f15;--phos:#00dd44;--phos-hot:#00ff55;--dim:#5a6b60;--line:#1e2630;--text:#e8e6d9;--purple:#9e76ff;--gold:#c9a84c;--amber:#ffb347;--red:#ff4466;--cyan:#66bbff}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Courier New',Menlo,monospace;background:var(--bg);color:var(--text);line-height:1.55;padding-bottom:3rem}
|
||||
a{color:var(--phos);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
|
||||
body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);line-height:1.55;padding-bottom:3rem}
|
||||
a{color:var(--phos);text-decoration:none}a:hover{text-decoration:underline}
|
||||
.help{color:var(--dim);font-size:.8rem;font-style:italic}
|
||||
/* ── HERO ── */
|
||||
.hero{position:relative;overflow:hidden;background:radial-gradient(120% 120% at 50% -10%,#221041 0%,#0a0a0f 60%);padding:3rem 1.5rem 2.4rem;text-align:center;border-bottom:2px solid var(--phos)}
|
||||
.eye{font-size:3.4rem;line-height:1;display:inline-block;animation:gaze 5s ease-in-out infinite;filter:drop-shadow(0 0 14px rgba(0,255,85,0.55))}
|
||||
.hero{position:relative;overflow:hidden;background:radial-gradient(120% 120% at 50% -10%,#1a1030 0%,#0a0a0f 62%);padding:3rem 1.5rem 2.4rem;text-align:center;border-bottom:1px solid var(--line)}
|
||||
.eye{font-size:3.4rem;line-height:1;display:inline-block;animation:gaze 5s ease-in-out infinite;filter:drop-shadow(0 0 14px rgba(0,255,85,.5))}
|
||||
@keyframes gaze{0%,100%{transform:translateX(0) scale(1)}25%{transform:translateX(-6px) scale(1.04)}60%{transform:translateX(7px) scale(1.04)}}
|
||||
.hero h1{font-size:2.6rem;color:var(--phos-hot);text-shadow:0 0 10px var(--phos);letter-spacing:0.08em;margin-top:0.3rem}
|
||||
.hero .punch{color:var(--text);font-size:1.25rem;margin-top:0.6rem;font-weight:bold}
|
||||
.hero .punch b{color:var(--gold)}
|
||||
.hero .sub{color:var(--dim);font-size:0.82rem;margin-top:0.5rem;max-width:560px;margin-left:auto;margin-right:auto}
|
||||
/* floating tracker dots = "who's watching" */
|
||||
.hero h1{font-size:2.4rem;color:var(--phos-hot);letter-spacing:.06em;margin-top:.3rem;font-weight:800}
|
||||
.hero .punch{color:var(--text);font-size:1.2rem;margin-top:.6rem;font-weight:700}.hero .punch b{color:var(--gold)}
|
||||
.hero .sub{color:var(--dim);font-size:.82rem;margin-top:.5rem;max-width:560px;margin-left:auto;margin-right:auto}
|
||||
.dots{position:absolute;inset:0;pointer-events:none;z-index:0}
|
||||
.dots i{position:absolute;width:7px;height:7px;border-radius:50%;opacity:0.0;animation:float 7s ease-in-out infinite}
|
||||
.dots i:nth-child(1){left:12%;top:30%;background:var(--cyan);animation-delay:.0s}
|
||||
.dots i{position:absolute;width:7px;height:7px;border-radius:50%;opacity:0;animation:float 7s ease-in-out infinite}
|
||||
.dots i:nth-child(1){left:12%;top:30%;background:var(--cyan);animation-delay:0s}
|
||||
.dots i:nth-child(2){left:82%;top:24%;background:var(--amber);animation-delay:1.1s}
|
||||
.dots i:nth-child(3){left:24%;top:68%;background:var(--red);animation-delay:2.3s}
|
||||
.dots i:nth-child(4){left:70%;top:64%;background:var(--purple);animation-delay:.7s}
|
||||
.dots i:nth-child(5){left:50%;top:14%;background:var(--cyan);animation-delay:3.0s}
|
||||
.dots i:nth-child(5){left:50%;top:14%;background:var(--cyan);animation-delay:3s}
|
||||
.dots i:nth-child(6){left:90%;top:54%;background:var(--red);animation-delay:1.8s}
|
||||
@keyframes float{0%{opacity:0;transform:translateY(8px) scale(.6)}30%{opacity:.85}70%{opacity:.7}100%{opacity:0;transform:translateY(-14px) scale(1.1)}}
|
||||
.hero>*{position:relative;z-index:1}
|
||||
|
||||
/* ── big CTA row ── */
|
||||
.ctas{margin-top:1.4rem;display:flex;gap:0.6rem;justify-content:center;flex-wrap:wrap}
|
||||
.cta{display:inline-block;padding:0.85rem 1.6rem;font-weight:bold;border-radius:8px;font-size:1.02rem;text-shadow:none;transition:transform .12s,box-shadow .12s}
|
||||
/* ── CTA row ── */
|
||||
.ctas{margin-top:1.4rem;display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap}
|
||||
.cta{display:inline-block;padding:.85rem 1.6rem;font-weight:700;border-radius:10px;font-size:1.02rem;transition:transform .12s}
|
||||
.cta:hover{text-decoration:none;transform:translateY(-2px)}
|
||||
.cta.go{background:var(--phos);color:#0a0a0f;box-shadow:0 4px 18px rgba(0,221,68,0.4)}
|
||||
.cta.go:hover{box-shadow:0 6px 24px rgba(0,221,68,0.6)}
|
||||
.cta.go{background:var(--phos);color:#06140a;box-shadow:0 4px 18px rgba(0,221,68,.35)}
|
||||
.cta.alt{background:transparent;color:var(--purple);border:1px solid var(--purple)}
|
||||
.cta.alt:hover{background:rgba(158,118,255,0.12)}
|
||||
|
||||
/* ── quicknav (trimmed) ── */
|
||||
.quicknav{display:flex;flex-wrap:wrap;justify-content:center;gap:0.6rem;margin-top:1.4rem;max-width:620px;margin-left:auto;margin-right:auto}
|
||||
.qi{display:flex;flex-direction:column;align-items:center;gap:4px;padding:0.5rem 0.4rem;min-width:74px;background:rgba(110,64,201,0.08);border:1px solid var(--purple);border-radius:8px;text-decoration:none;color:var(--text);transition:all 0.12s;font-family:inherit}
|
||||
.qi:hover{background:rgba(110,64,201,0.22);transform:translateY(-2px);box-shadow:0 4px 14px rgba(158,118,255,0.35);text-decoration:none}
|
||||
.qi-emoji{font-size:1.5rem;line-height:1}
|
||||
.qi-label{font-size:0.62rem;letter-spacing:0.04em;color:var(--phos-hot);font-weight:bold;white-space:nowrap}
|
||||
|
||||
.container{max-width:1080px;margin:auto;padding:2rem 1.5rem}
|
||||
.section{margin-bottom:2.5rem}
|
||||
h2{color:var(--phos-hot);text-shadow:0 0 4px var(--phos);font-size:1.3rem;margin-bottom:0.8rem;border-bottom:1px solid var(--dim);padding-bottom:0.4rem;letter-spacing:0.04em}
|
||||
h3{color:var(--purple);font-size:1rem;margin-bottom:0.5rem}
|
||||
.grid{display:grid;gap:1rem}
|
||||
.grid-2{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
|
||||
.grid-4{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
|
||||
.card{border:1px solid var(--dim);background:var(--bg2);padding:1rem 1.2rem;border-radius:4px}
|
||||
.card.purple{border-color:var(--purple);background:rgba(110,64,201,0.05)}
|
||||
.card.amber{border-color:var(--amber);background:rgba(255,179,71,0.05)}
|
||||
.kpi{text-align:center;padding:1rem;background:rgba(0,221,68,0.05);border:1px solid var(--phos);border-radius:4px}
|
||||
.kpi .v{font-size:2rem;font-weight:bold;color:var(--phos-hot);text-shadow:0 0 6px var(--phos);display:block}
|
||||
.kpi .l{font-size:0.75rem;color:var(--dim)}
|
||||
.level{display:flex;align-items:start;gap:0.8rem;padding:0.9rem;border-radius:4px}
|
||||
.level .emj{font-size:1.8rem;flex-shrink:0}
|
||||
.level .body{flex:1}
|
||||
.level .body b{display:block;font-size:1rem;margin-bottom:0.2rem}
|
||||
.level .body .desc{font-size:0.85rem;color:var(--text);opacity:0.85}
|
||||
.level.r0{background:rgba(255,255,255,0.03);border:1px solid var(--dim)}
|
||||
.level.r1{background:rgba(0,221,68,0.08);border:1px solid var(--phos);color:var(--phos-hot)}
|
||||
.level.r2{background:rgba(255,179,71,0.08);border:1px solid var(--amber)}
|
||||
.level.r3{background:rgba(158,118,255,0.08);border:1px solid var(--purple)}
|
||||
.tag-recommended{display:inline-block;background:var(--phos);color:#0a0a0f;font-size:0.65rem;padding:0.1rem 0.4rem;border-radius:99px;font-weight:bold;margin-left:0.3rem;vertical-align:middle}
|
||||
.tag-new{display:inline-block;background:var(--purple);color:#fff;font-size:0.65rem;padding:0.1rem 0.4rem;border-radius:99px;font-weight:bold;margin-left:0.3rem;vertical-align:middle}
|
||||
.cta.alt:hover{background:rgba(158,118,255,.12)}
|
||||
/* ── quicknav ── */
|
||||
.quicknav{display:flex;flex-wrap:wrap;justify-content:center;gap:.6rem;margin:1.4rem auto 0;max-width:620px}
|
||||
.qi{display:flex;flex-direction:column;align-items:center;gap:4px;padding:.55rem .45rem;min-width:74px;background:var(--soft);border:1px solid var(--line);border-radius:12px;color:var(--text);transition:.12s;font-family:inherit}
|
||||
.qi:hover{border-color:var(--purple);transform:translateY(-2px);text-decoration:none}
|
||||
.qi-emoji{font-size:1.5rem;line-height:1}.qi-label{font-size:.62rem;letter-spacing:.04em;color:var(--phos-hot);font-weight:700;white-space:nowrap}
|
||||
/* ── layout ── */
|
||||
.container{max-width:760px;margin:auto;padding:1.6rem 1.1rem}
|
||||
.section{margin-bottom:1.7rem}
|
||||
h2{color:var(--phos-hot);font-size:1.12rem;margin-bottom:.6rem;letter-spacing:.02em}
|
||||
h3{color:var(--purple);font-size:.95rem;margin-bottom:.4rem}
|
||||
.grid{display:grid;gap:1rem}.grid-2{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}.grid-4{grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
|
||||
.card{border:1px solid var(--line);background:var(--panel);padding:1rem 1.1rem;border-radius:12px}
|
||||
.card.purple{border-color:rgba(158,118,255,.4)}.card.amber{border-color:rgba(255,179,71,.4)}
|
||||
.kpi{text-align:center;padding:.8rem .4rem;background:var(--soft);border:1px solid var(--line);border-radius:12px}
|
||||
.kpi .v{font-size:1.7rem;font-weight:800;color:var(--phos-hot);display:block}.kpi .l{font-size:.66rem;color:var(--dim)}
|
||||
.level{display:flex;align-items:start;gap:.8rem;padding:.85rem;border-radius:12px}
|
||||
.level .emj{font-size:1.7rem;flex-shrink:0}.level .body{flex:1}.level .body b{display:block;font-size:.98rem;margin-bottom:.2rem}.level .body .desc{font-size:.84rem;color:var(--text);opacity:.85}
|
||||
.level.r0{background:var(--soft);border:1px solid var(--line)}.level.r1{background:rgba(0,221,68,.08);border:1px solid var(--phos)}.level.r2{background:rgba(255,179,71,.08);border:1px solid var(--amber)}.level.r3{background:rgba(158,118,255,.08);border:1px solid var(--purple)}
|
||||
.tag-recommended{display:inline-block;background:var(--phos);color:#06140a;font-size:.65rem;padding:.1rem .45rem;border-radius:99px;font-weight:700;margin-left:.3rem;vertical-align:middle}
|
||||
.tag-new{display:inline-block;background:var(--purple);color:#fff;font-size:.65rem;padding:.1rem .45rem;border-radius:99px;font-weight:700;margin-left:.3rem;vertical-align:middle}
|
||||
svg.chart{width:100%;max-width:400px;height:auto}
|
||||
.svg-bar{fill:var(--phos);transition:fill 0.3s}
|
||||
.svg-bar.medium{fill:var(--amber)}
|
||||
.svg-bar.high{fill:var(--red)}
|
||||
code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;color:var(--phos-hot)}
|
||||
.cta-sm{display:inline-block;background:var(--phos);color:#0a0a0f;padding:0.7rem 1.4rem;text-decoration:none;font-weight:bold;border-radius:4px;margin:0.5rem 0.3rem 0.5rem 0;text-shadow:none}
|
||||
.cta-sm.outline{background:transparent;color:var(--phos);border:1px solid var(--phos)}
|
||||
.footer{text-align:center;font-size:0.78rem;color:var(--dim);padding:1.5rem;border-top:1px solid var(--dim);margin-top:2rem}
|
||||
.arch{font-family:monospace;font-size:0.75rem;color:var(--phos-hot);text-shadow:0 0 4px var(--phos);background:var(--bg2);padding:1rem;border:1px solid var(--dim);border-radius:4px;overflow-x:auto;white-space:pre;line-height:1.4}
|
||||
|
||||
/* ── install panel (kept up top) ── */
|
||||
.install-panel{background:rgba(0,255,65,0.04);border:1px solid rgba(0,255,65,0.25);border-radius:6px;padding:0.6rem 0.9rem;margin:0.45rem 0;text-align:left}
|
||||
.install-panel summary{cursor:pointer;font-size:0.95rem;color:var(--phos-hot);list-style:none;outline:none}
|
||||
.svg-bar{fill:var(--phos)}.svg-bar.medium{fill:var(--amber)}.svg-bar.high{fill:var(--red)}
|
||||
code{background:var(--soft);padding:.1rem .4rem;border-radius:4px;font-size:.82rem;color:var(--phos-hot);font-family:ui-monospace,Menlo,monospace}
|
||||
.cta-sm{display:inline-block;background:var(--phos);color:#06140a;padding:.6rem 1.2rem;font-weight:700;border-radius:10px;margin:.4rem .3rem .4rem 0}.cta-sm.outline{background:transparent;color:var(--phos);border:1px solid var(--phos)}
|
||||
.footer{text-align:center;font-size:.7rem;color:var(--dim);padding:1.4rem;border-top:1px solid var(--line);margin-top:2rem}
|
||||
.arch{font-family:ui-monospace,Menlo,monospace;font-size:.74rem;color:var(--phos-hot);background:var(--soft);padding:1rem;border:1px solid var(--line);border-radius:12px;overflow-x:auto;white-space:pre;line-height:1.4}
|
||||
/* ── install panel ── */
|
||||
.install-panel{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:.7rem 1rem;margin:.5rem 0;text-align:left}
|
||||
.install-panel summary{cursor:pointer;font-size:.95rem;color:var(--phos-hot);list-style:none;outline:none;font-weight:700}
|
||||
.install-panel summary::-webkit-details-marker{display:none}
|
||||
.install-panel[open] summary{margin-bottom:0.6rem}
|
||||
.install-panel .emoji{font-size:1.1rem;margin-right:0.3rem}
|
||||
.install-panel ol{padding-left:1.1rem;line-height:1.5;font-size:0.85rem}
|
||||
.install-panel .btn{display:inline-block;padding:0.45rem 0.75rem;margin:0.25rem 0.2rem 0.25rem 0;background:var(--purple);color:#fff;text-decoration:none;border-radius:5px;font-weight:bold;font-size:0.82rem}
|
||||
.install-panel[open] summary{margin-bottom:.6rem}
|
||||
.install-panel .emoji{font-size:1.1rem;margin-right:.3rem}
|
||||
.install-panel ol{padding-left:1.1rem;line-height:1.5;font-size:.85rem}
|
||||
.install-panel .btn{display:inline-block;padding:.45rem .8rem;margin:.25rem .2rem .25rem 0;background:var(--purple);color:#fff;text-decoration:none;border-radius:8px;font-weight:700;font-size:.82rem}
|
||||
.install-panel .btn.alt{background:transparent;border:1px solid var(--purple);color:var(--purple)}
|
||||
.install-panel code{background:rgba(0,0,0,0.4);padding:0.1rem 0.35rem;border-radius:3px;font-size:0.8rem;color:var(--phos-hot)}
|
||||
.install-panel .note{color:var(--dim);font-size:0.78rem;margin-top:0.6rem;border-left:2px solid var(--amber);padding-left:0.6rem}
|
||||
.install-panel img{max-width:100%;border-radius:5px;margin:0.4rem 0}
|
||||
.install-panel pre{background:rgba(0,0,0,0.4);padding:0.5rem 0.7rem;border-radius:4px;overflow-x:auto;font-size:0.78rem;margin:0.4rem 0}
|
||||
|
||||
.install-panel code{background:var(--soft);padding:.1rem .35rem;border-radius:4px;font-size:.8rem;color:var(--phos-hot)}
|
||||
.install-panel .note{color:var(--dim);font-size:.78rem;margin-top:.6rem;border-left:2px solid var(--amber);padding-left:.6rem}
|
||||
.install-panel img{max-width:100%;border-radius:8px;margin:.4rem 0}
|
||||
.install-panel pre{background:var(--soft);padding:.5rem .7rem;border-radius:8px;overflow-x:auto;font-size:.76rem;margin:.4rem 0}
|
||||
/* ── "En savoir plus" fold ── */
|
||||
.more{max-width:1080px;margin:0 auto;padding:0 1.5rem}
|
||||
.more>summary{cursor:pointer;list-style:none;text-align:center;color:var(--purple);font-size:0.95rem;letter-spacing:0.05em;padding:0.9rem;border:1px dashed var(--purple);border-radius:8px;margin-bottom:1rem;transition:background .12s}
|
||||
.more{max-width:760px;margin:0 auto;padding:0 1.1rem}
|
||||
.more>summary{cursor:pointer;list-style:none;text-align:center;color:var(--purple);font-size:.92rem;letter-spacing:.04em;padding:.85rem;border:1px solid var(--line);border-radius:12px;margin-bottom:1rem;transition:background .12s}
|
||||
.more>summary::-webkit-details-marker{display:none}
|
||||
.more>summary:hover{background:rgba(158,118,255,0.1)}
|
||||
.more[open]>summary{margin-bottom:1.6rem}
|
||||
.more>summary:hover{background:rgba(158,118,255,.08)}
|
||||
.more[open]>summary{margin-bottom:1.4rem}
|
||||
.more>summary .chev{display:inline-block;transition:transform .2s}
|
||||
.more[open]>summary .chev{transform:rotate(90deg)}
|
||||
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
||||
.v.tick{animation:flash 0.6s}
|
||||
@keyframes flash{0%{color:var(--gold);transform:scale(1.15)}100%{color:var(--phos-hot);transform:scale(1)}}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.v.tick{animation:flash .6s}@keyframes flash{0%{color:var(--gold);transform:scale(1.15)}100%{color:var(--phos-hot);transform:scale(1)}}
|
||||
</style></head><body>
|
||||
|
||||
<div class=hero>
|
||||
|
|
@ -123,19 +100,17 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
|
||||
<div class=ctas>
|
||||
<a href="/wg/r3-install" class="cta go">✨ Protège-moi (R3)</a>
|
||||
<a href="/social/me" class="cta alt">🕸️ Qui me piste ?</a>
|
||||
<a href="/social/me{{ '?mh=' + mac_hash if mac_hash else '' }}" class="cta alt">🕸️ Qui me piste ?</a>
|
||||
</div>
|
||||
|
||||
{# trimmed quick-nav — CA iPhone / CA Android / QR profil moved into the
|
||||
per-platform install panel below (#543) #}
|
||||
<div class=quicknav>
|
||||
<a href="/wg/r3-install" class=qi title="Installer R3 WireGuard">
|
||||
<span class=qi-emoji>🌐</span><span class=qi-label>R3 Install</span>
|
||||
</a>
|
||||
<a href="/report/me/html" class=qi title="Mon rapport live">
|
||||
<a href="/report/me/html{{ '?mh=' + mac_hash if mac_hash else '' }}" class=qi title="Mon rapport live">
|
||||
<span class=qi-emoji>📊</span><span class=qi-label>Mon rapport</span>
|
||||
</a>
|
||||
<a href="/social/me" class=qi title="Cartographie sociale — qui me piste, où ?">
|
||||
<a href="/social/me{{ '?mh=' + mac_hash if mac_hash else '' }}" class=qi title="Cartographie sociale — qui me piste, où ?">
|
||||
<span class=qi-emoji>🕸️</span><span class=qi-label>Ma carto</span>
|
||||
</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb/wiki/R3-WireGuard-install" class=qi title="Wiki R3 multi-OS">
|
||||
|
|
@ -151,12 +126,12 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
<div class=container>
|
||||
<div class=section style="margin-bottom:1.5rem">
|
||||
<h2>📥 Installe en 1 tap</h2>
|
||||
<p style="font-size:0.85rem;color:var(--dim);margin-bottom:0.8rem">
|
||||
<p class=help style="margin-bottom:.8rem">
|
||||
On a détecté <code>{{ install_platform }}</code> — le panneau adapté est ouvert.
|
||||
Le CA, le QR et le profil sont dedans. Autre appareil ? Déplie le bon panneau.
|
||||
</p>
|
||||
{{ install_panels | safe }}
|
||||
<p style="margin-top:0.8rem;font-size:0.78rem;color:var(--dim)">
|
||||
<p class=help style="margin-top:.8rem">
|
||||
R3 marche hors-cabine (4G/5G, autre WiFi), couvre tout le HTTPS, et se révoque
|
||||
à tout moment. Page standalone : <a href=/wg/onboard>/wg/onboard</a>.
|
||||
</p>
|
||||
|
|
@ -170,8 +145,8 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
{# ── KPI live (auto-refresh 5s via /cumulative-stats.json) ── #}
|
||||
<div class=section>
|
||||
<h2>📊 Cabine en chiffres
|
||||
<span id=live-badge style="font-size:0.7rem;background:var(--red);color:#fff;padding:0.15rem 0.5rem;border-radius:99px;margin-left:0.5rem;letter-spacing:0.05em;animation:pulse 1.5s infinite">🔴 LIVE</span>
|
||||
<span id=live-stamp style="font-size:0.7rem;color:var(--dim);margin-left:0.4rem">·</span>
|
||||
<span id=live-badge style="font-size:.7rem;background:var(--red);color:#fff;padding:.15rem .5rem;border-radius:99px;margin-left:.5rem;letter-spacing:.04em;animation:pulse 1.5s infinite">🔴 LIVE</span>
|
||||
<span id=live-stamp style="font-size:.7rem;color:var(--dim);margin-left:.4rem">·</span>
|
||||
</h2>
|
||||
<div class="grid grid-4">
|
||||
<div class=kpi><span class=v data-live=sessions.last_7d>{{ stats.sessions.last_7d if stats.sessions else 0 }}</span><span class=l>👥 sessions uniques (7j)</span></div>
|
||||
|
|
@ -193,16 +168,16 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
<div class="card purple" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||||
<div style="font-size:2.4rem;flex-shrink:0" id=cert-probe-emoji>❔</div>
|
||||
<div style="flex:1;min-width:220px">
|
||||
<p id=cert-probe-text style="font-size:0.92rem;line-height:1.5">
|
||||
<p id=cert-probe-text style="font-size:.92rem;line-height:1.5">
|
||||
Clique sur <b>Tester</b> pour vérifier que ton CA R3 est bien installé sur ton device
|
||||
(depuis le tunnel WireGuard). Le test télécharge une image HTTPS et vérifie le succès.
|
||||
</p>
|
||||
<p style="font-size:0.75rem;color:var(--dim);margin-top:0.3rem">
|
||||
<p class=help style="margin-top:.3rem">
|
||||
Empreinte SHA1 CA R3 (à vérifier dans Réglages iPhone) :
|
||||
<code id=cert-fp-r3 style="font-size:0.7rem">…</code>
|
||||
<code id=cert-fp-r3 style="font-size:.7rem">…</code>
|
||||
</p>
|
||||
</div>
|
||||
<button id=cert-probe-btn style="background:var(--purple);color:#0a0a0f;border:0;padding:0.7rem 1.2rem;font-weight:bold;border-radius:4px;cursor:pointer;font-family:inherit;font-size:0.9rem">🔬 Tester</button>
|
||||
<button id=cert-probe-btn style="background:var(--purple);color:#06140a;border:0;padding:.7rem 1.2rem;font-weight:700;border-radius:10px;cursor:pointer;font-family:inherit;font-size:.9rem">🔬 Tester</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -215,7 +190,7 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
niveau d'analyse, tu obtiens un rapport détaillé sur les apps, trackers, certificats,
|
||||
et risques observés pendant ta session.
|
||||
</p>
|
||||
<p style="margin-top:0.6rem">
|
||||
<p style="margin-top:.6rem">
|
||||
<b>Conçu par CyberMind / Gérald Kerma</b> à Notre-Dame-du-Cruet (Savoie). Conforme
|
||||
<b>CSPN ANSSI</b> + <b>LCEN</b> : consentement explicite, hash MAC quotidien rotatif,
|
||||
données effacées après 24h, aucun envoi externe.
|
||||
|
|
@ -248,10 +223,10 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
</div>
|
||||
</div>
|
||||
<div class="level r3">
|
||||
<span class=emj>🌐</span>
|
||||
<span class=emj>🧅</span>
|
||||
<div class=body>
|
||||
<b>R3 — WireGuard portable<span class=tag-new>NOUVEAU</span></b>
|
||||
<p class=desc>VPN tunnel mitm. Bandeau sur TOUT (HTTPS + QUIC). <b>Marche hors VILLAGE3B</b> (4G/5G, autre WiFi). Profile install 1 tap via QR.</p>
|
||||
<p class=desc>VPN tunnel mitm. Bandeau sur TOUT (HTTPS + QUIC). <b>Marche hors VILLAGE3B</b> (4G/5G, autre WiFi). Option sortie 🧅 Tor anonymisée. Profile install 1 tap via QR.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -269,14 +244,15 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
{% set mpct = (risk.medium * 100 / total) | round(0) | int %}
|
||||
{% set hpct = (risk.high * 100 / total) | round(0) | int %}
|
||||
<svg class=chart viewBox="0 0 300 60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x=0 y=20 width="{{ lpct * 3 }}" height=20 class=svg-bar/>
|
||||
<rect x=0 y=20 width="{{ lpct * 3 }}" height=20 rx=4 class=svg-bar/>
|
||||
<rect x="{{ lpct * 3 }}" y=20 width="{{ mpct * 3 }}" height=20 class="svg-bar medium"/>
|
||||
<rect x="{{ (lpct + mpct) * 3 }}" y=20 width="{{ hpct * 3 }}" height=20 class="svg-bar high"/>
|
||||
<text x=10 y=15 fill="#00ff55" font-family=monospace font-size=10>🟢 {{ lpct }}% LOW</text>
|
||||
<text x="{{ lpct * 3 + 10 }}" y=15 fill="#ffb347" font-family=monospace font-size=10>🟡 {{ mpct }}% MED</text>
|
||||
<text x="{{ (lpct + mpct) * 3 + 10 }}" y=15 fill="#ff4466" font-family=monospace font-size=10>🔴 {{ hpct }}% HI</text>
|
||||
<text x=10 y=55 fill="#666" font-family=monospace font-size=8>{{ total }} sessions analysées</text>
|
||||
<rect x="{{ (lpct + mpct) * 3 }}" y=20 width="{{ hpct * 3 }}" height=20 rx=4 class="svg-bar high"/>
|
||||
<text x=10 y=15 fill="#00ff55" font-size=10>🟢 {{ lpct }}% LOW</text>
|
||||
<text x="{{ lpct * 3 + 10 }}" y=15 fill="#ffb347" font-size=10>🟡 {{ mpct }}% MED</text>
|
||||
<text x="{{ (lpct + mpct) * 3 + 10 }}" y=15 fill="#ff4466" font-size=10>🔴 {{ hpct }}% HI</text>
|
||||
<text x=10 y=55 fill="#5a6b60" font-size=8>{{ total }} sessions analysées</text>
|
||||
</svg>
|
||||
<p class=help>La plupart des sessions sont à faible risque.</p>
|
||||
</div>
|
||||
<div class=card>
|
||||
<h3>🛡 Niveau d'opt-in choisi par les visiteurs</h3>
|
||||
|
|
@ -284,15 +260,16 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
{% set ltotal = (lvl.r0 + lvl.r1 + lvl.r2 + lvl.r3) or 1 %}
|
||||
<svg class=chart viewBox="0 0 300 90" xmlns="http://www.w3.org/2000/svg">
|
||||
{% set ws = [(lvl.r0 * 300 / ltotal)|round|int, (lvl.r1 * 300 / ltotal)|round|int, (lvl.r2 * 300 / ltotal)|round|int, (lvl.r3 * 300 / ltotal)|round|int] %}
|
||||
<rect x=0 y=10 width="{{ ws[0] }}" height=15 fill="#666"/>
|
||||
<rect x=0 y=30 width="{{ ws[1] }}" height=15 fill="#00dd44"/>
|
||||
<rect x=0 y=50 width="{{ ws[2] }}" height=15 fill="#ffb347"/>
|
||||
<rect x=0 y=70 width="{{ ws[3] }}" height=15 fill="#9e76ff"/>
|
||||
<text x="{{ ws[0] + 5 }}" y=22 fill="#888" font-family=monospace font-size=10>🌐 R0 ({{ lvl.r0 }})</text>
|
||||
<text x="{{ ws[1] + 5 }}" y=42 fill="#00ff55" font-family=monospace font-size=10>🛡 R1 ({{ lvl.r1 }})</text>
|
||||
<text x="{{ ws[2] + 5 }}" y=62 fill="#ffd6a0" font-family=monospace font-size=10>🔍 R2 ({{ lvl.r2 }})</text>
|
||||
<text x="{{ ws[3] + 5 }}" y=82 fill="#cbb6ff" font-family=monospace font-size=10>🌐 R3 ({{ lvl.r3 }})</text>
|
||||
<rect x=0 y=10 width="{{ ws[0] }}" height=15 rx=4 fill="#5a6b60"/>
|
||||
<rect x=0 y=30 width="{{ ws[1] }}" height=15 rx=4 fill="#00dd44"/>
|
||||
<rect x=0 y=50 width="{{ ws[2] }}" height=15 rx=4 fill="#ffb347"/>
|
||||
<rect x=0 y=70 width="{{ ws[3] }}" height=15 rx=4 fill="#9e76ff"/>
|
||||
<text x="{{ ws[0] + 5 }}" y=22 fill="#888" font-size=10>🌐 R0 ({{ lvl.r0 }})</text>
|
||||
<text x="{{ ws[1] + 5 }}" y=42 fill="#00ff55" font-size=10>🛡 R1 ({{ lvl.r1 }})</text>
|
||||
<text x="{{ ws[2] + 5 }}" y=62 fill="#ffd6a0" font-size=10>🔍 R2 ({{ lvl.r2 }})</text>
|
||||
<text x="{{ ws[3] + 5 }}" y=82 fill="#cbb6ff" font-size=10>🧅 R3 ({{ lvl.r3 }})</text>
|
||||
</svg>
|
||||
<p class=help>R1 (analyse passive) est le choix le plus courant.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -303,7 +280,7 @@ code{background:#222;padding:0.1rem 0.4rem;border-radius:2px;font-size:0.85rem;c
|
|||
<div class=arch>
|
||||
LXC mitmproxy 10.100.0.60 WAF (vhosts CyberMind)
|
||||
LXC toolbox-mitm 10.100.0.61 R1/R2 transparent
|
||||
LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
||||
LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard (+🧅 Tor)
|
||||
↑
|
||||
CAs séparées · addons partagés · DB unifiée
|
||||
↑
|
||||
|
|
@ -318,7 +295,7 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
Le code est <b>publié intégralement</b> sous licence <b>CyberMind Source-Disclosed v1.0</b>
|
||||
(audit citoyen possible, droits d'usage régis par licence CMSD). Pas de boîte noire.
|
||||
</p>
|
||||
<div style="margin-top:0.6rem">
|
||||
<div style="margin-top:.6rem">
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb" class=cta-sm>📂 Code source GitHub</a>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb/blob/master/LICENCE-CMSD-1.0.md" class="cta-sm outline">📜 Licence CMSD-1.0</a>
|
||||
</div>
|
||||
|
|
@ -326,11 +303,11 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
|
||||
{# ── Contact ── #}
|
||||
<div class=section>
|
||||
<h2>📡 Contact & soutiens</h2>
|
||||
<h2>📡 Contact & soutiens</h2>
|
||||
<div class="grid grid-2">
|
||||
<div class=card>
|
||||
<h3>💚 Soutenir le projet</h3>
|
||||
<ul style="list-style:none;padding-left:0;font-size:0.85rem">
|
||||
<ul style="list-style:none;padding-left:0;font-size:.85rem">
|
||||
<li>💰 Don récurrent : <a href="https://liberapay.com/cybermind">liberapay.com/cybermind</a></li>
|
||||
<li>💳 Don ponctuel : <a href="https://cybermind.fr/don">cybermind.fr/don</a></li>
|
||||
<li>📧 Support : <a href="mailto:support@cybermind.fr">support@cybermind.fr</a></li>
|
||||
|
|
@ -338,7 +315,7 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
</div>
|
||||
<div class="card purple">
|
||||
<h3>🏢 Déploiement collectivité</h3>
|
||||
<ul style="list-style:none;padding-left:0;font-size:0.85rem">
|
||||
<ul style="list-style:none;padding-left:0;font-size:.85rem">
|
||||
<li>📡 Borne grand public : <a href="mailto:gondwana@cybermind.fr">gondwana@cybermind.fr</a></li>
|
||||
<li>🎓 Formation cybersécurité : <a href="mailto:contact@cybermind.fr">contact@cybermind.fr</a></li>
|
||||
<li>🛡 Audit SecuBox premium : <a href="mailto:contact@cybermind.fr">contact@cybermind.fr</a></li>
|
||||
|
|
@ -376,7 +353,6 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
var ss = String(d.getSeconds()).padStart(2,'0');
|
||||
return 'maj ' + hh+':'+mm+':'+ss;
|
||||
}
|
||||
// count-up: animate each KPI from 0 → its server-rendered value, once.
|
||||
function countUp(el, target){
|
||||
var start = 0, dur = 900, t0 = null;
|
||||
function step(ts){
|
||||
|
|
@ -403,7 +379,7 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
if (prev !== next) {
|
||||
el.textContent = next;
|
||||
el.classList.remove('tick');
|
||||
void el.offsetWidth; // force reflow
|
||||
void el.offsetWidth;
|
||||
el.classList.add('tick');
|
||||
}
|
||||
});
|
||||
|
|
@ -449,8 +425,8 @@ LXC toolbox-mitm-wg 10.100.0.62 R3 WireGuard
|
|||
} else {
|
||||
emj.textContent = '🔴';
|
||||
txt.innerHTML = '<b>Tunnel R3 actif mais CA R3 NON trusté</b> — HTTPS casse. ' +
|
||||
'Installe le <a href=/wg/ca.mobileconfig style="color:var(--orange)">profil CA R3 iPhone</a> ' +
|
||||
'ou le <a href=/wg/ca.pem style="color:var(--orange)">ca.pem Android/PC</a>.';
|
||||
'Installe le <a href=/wg/ca.mobileconfig style="color:var(--amber)">profil CA R3 iPhone</a> ' +
|
||||
'ou le <a href=/wg/ca.pem style="color:var(--amber)">ca.pem Android/PC</a>.';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,662 +1,325 @@
|
|||
{# SPDX-License-Identifier: LicenseRef-CMSD-1.0 #}
|
||||
{# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr> #}
|
||||
{# #683 report redesign — verdict-first, graphs, plain-language helpers, deep
|
||||
technical cards collapsed into <details>. Same data model as before. #}
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr"><head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta http-equiv="refresh" content="15">
|
||||
{# Phase 3 (#492) : PWA tags for iOS Add-to-Home-Screen webclip experience #}
|
||||
<meta http-equiv="refresh" content="20">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="ToolBoX Cabine">
|
||||
<meta name="theme-color" content="#0a0a0f">
|
||||
<title>Mon rapport Gondwana ToolBoX — live</title>
|
||||
<title>Mon rapport — VILLAGE3B</title>
|
||||
<style>
|
||||
:root{--bg:#0a0a0f;--phos:#00dd44;--phos-hot:#00ff55;--dim:#006622;--text:#e8e6d9;--red:#ff4466;--amber:#ffb347}
|
||||
:root{--bg:#0a0a0f;--panel:#11131a;--phos:#00dd44;--phos-hot:#00ff55;--dim:#5a6b60;--line:#1e2630;--text:#e8e6d9;--red:#ff4466;--amber:#ffb347;--violet:#9e76ff;--blue:#66bbff}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Courier New',Menlo,monospace;background:var(--bg);color:var(--text);padding:1rem;max-width:760px;margin:auto;line-height:1.5}
|
||||
h1{color:var(--phos-hot);text-shadow:0 0 6px var(--phos);font-size:1.6rem;margin-bottom:0.3rem;letter-spacing:0.05em}
|
||||
.sub{color:var(--dim);font-size:0.85rem;margin-bottom:1rem;letter-spacing:0.05em}
|
||||
.card{border:1px solid var(--dim);background:rgba(0,221,68,0.03);padding:0.9rem 1rem;margin-bottom:1rem}
|
||||
.card h2{color:var(--phos-hot);text-shadow:0 0 4px var(--phos);font-size:0.95rem;margin-bottom:0.5rem;border-bottom:1px solid var(--dim);padding-bottom:0.3rem;letter-spacing:0.05em}
|
||||
.kv{display:grid;grid-template-columns:auto 1fr;gap:0.2rem 0.8rem;font-size:0.85rem}
|
||||
.kv .k{color:var(--dim)}
|
||||
.kv .v{color:var(--phos);text-shadow:0 0 4px var(--phos)}
|
||||
ul{list-style:none;padding-left:0.6rem}
|
||||
li{padding:0.15rem 0;font-size:0.85rem}
|
||||
li::before{content:"▸ ";color:var(--phos);text-shadow:0 0 4px var(--phos)}
|
||||
.score{display:inline-block;padding:0.3rem 1rem;font-size:1.2rem;font-weight:bold;border:2px solid;border-radius:4px}
|
||||
.score.low{color:var(--phos-hot);border-color:var(--phos);text-shadow:0 0 6px var(--phos)}
|
||||
.score.med{color:var(--amber);border-color:var(--amber);text-shadow:0 0 6px var(--amber)}
|
||||
.score.high{color:var(--red);border-color:var(--red);text-shadow:0 0 6px var(--red)}
|
||||
.pin{color:var(--amber)}
|
||||
.url{font-family:monospace;font-size:0.78rem;color:var(--text);background:rgba(0,221,68,0.05);padding:0.15rem 0.4rem;border-radius:2px;margin:0.1rem 0;display:inline-block;max-width:100%;overflow-wrap:break-word;word-break:break-all}
|
||||
.actions{text-align:center;margin:1.5rem 0}
|
||||
.actions a{display:inline-block;margin:0 0.3rem;padding:0.5rem 1rem;border:1px solid var(--phos);color:var(--phos-hot);text-decoration:none;text-shadow:0 0 4px var(--phos);font-size:0.85rem}
|
||||
.actions a:hover{background:rgba(0,221,68,0.1)}
|
||||
.refresh{text-align:center;font-size:0.7rem;color:var(--dim);margin-top:1rem;font-style:italic}
|
||||
.footer{text-align:center;font-size:0.65rem;color:var(--dim);margin-top:1.5rem;border-top:1px solid var(--dim);padding-top:0.6rem}
|
||||
body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);padding:1rem;max-width:740px;margin:auto;line-height:1.5}
|
||||
h1{color:var(--phos-hot);font-size:1.35rem;letter-spacing:.04em;display:flex;align-items:center;gap:.4rem}
|
||||
.sub{color:var(--dim);font-size:.82rem;margin-bottom:1.1rem}
|
||||
.help{color:var(--dim);font-size:.78rem;font-style:italic;margin-top:.25rem}
|
||||
.card{border:1px solid var(--line);background:var(--panel);border-radius:12px;padding:1rem 1.1rem;margin-bottom:1rem}
|
||||
.card h2{color:var(--phos-hot);font-size:.95rem;margin-bottom:.5rem;letter-spacing:.03em}
|
||||
/* ── verdict hero ── */
|
||||
.hero{text-align:center;background:radial-gradient(120% 120% at 50% 0%,rgba(0,221,68,.10),rgba(110,64,201,.05) 70%,transparent);border-color:var(--phos)}
|
||||
.gauge{width:170px;height:170px;border-radius:50%;margin:.4rem auto;display:flex;align-items:center;justify-content:center}
|
||||
.gauge-hole{width:124px;height:124px;border-radius:50%;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center}
|
||||
.gauge-num{font-size:2.6rem;font-weight:800;line-height:1}
|
||||
.gauge-max{font-size:.8rem;color:var(--dim)}
|
||||
.verdict{font-size:1.15rem;font-weight:700;margin-top:.3rem}
|
||||
/* ── KPI row ── */
|
||||
.kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem;margin-top:.4rem}
|
||||
.kpi{background:#0d0f15;border:1px solid var(--line);border-radius:10px;padding:.6rem .3rem;text-align:center}
|
||||
.kpi .e{font-size:1.15rem}
|
||||
.kpi .n{font-size:1.3rem;font-weight:800;color:var(--phos-hot)}
|
||||
.kpi .l{font-size:.62rem;color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
|
||||
/* ── graphs ── */
|
||||
.graphs{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
|
||||
@media(max-width:560px){.graphs{grid-template-columns:1fr}.kpis{grid-template-columns:repeat(2,1fr)}}
|
||||
.donut-wrap{display:flex;gap:.8rem;align-items:center}
|
||||
.donut{width:96px;height:96px;border-radius:50%;flex:0 0 auto}
|
||||
.donut-hole{width:54px;height:54px;border-radius:50%;background:var(--panel);margin:21px;display:flex;align-items:center;justify-content:center;font-size:.7rem;color:var(--dim);text-align:center}
|
||||
.legend{font-size:.78rem;display:flex;flex-direction:column;gap:.2rem;min-width:0}
|
||||
.legend .row{display:flex;align-items:center;gap:.35rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.dot{width:.6rem;height:.6rem;border-radius:50%;flex:0 0 auto}
|
||||
.bar-row{display:grid;grid-template-columns:5.5rem 1fr 2rem;gap:.45rem;align-items:center;font-size:.78rem;margin:.2rem 0}
|
||||
.bar-lbl{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.bar-track{background:#0d0f15;border-radius:99px;height:.7rem;overflow:hidden}
|
||||
.bar-fill{display:block;height:100%;border-radius:99px;background:linear-gradient(90deg,var(--phos),var(--phos-hot))}
|
||||
.bar-val{text-align:right;color:var(--phos);font-weight:700}
|
||||
.empty{color:var(--dim);font-size:.8rem;font-style:italic;padding:.4rem 0}
|
||||
/* ── details ── */
|
||||
details{border:1px solid var(--line);background:var(--panel);border-radius:12px;margin-bottom:1rem;overflow:hidden}
|
||||
details>summary{cursor:pointer;padding:.85rem 1.1rem;font-weight:700;color:var(--phos-hot);font-size:.92rem;list-style:none}
|
||||
details>summary::-webkit-details-marker{display:none}
|
||||
details>summary::before{content:"▸ ";color:var(--phos)}
|
||||
details[open]>summary::before{content:"▾ "}
|
||||
details .inner{padding:0 1.1rem 1rem}
|
||||
.kv{display:grid;grid-template-columns:auto 1fr;gap:.2rem .8rem;font-size:.84rem}
|
||||
.kv .k{color:var(--dim)}.kv .v{color:var(--phos)}
|
||||
table{width:100%;font-size:.8rem;border-collapse:collapse}
|
||||
th{color:var(--dim);text-align:left;font-weight:600;border-bottom:1px solid var(--line);padding:.25rem .3rem}
|
||||
td{padding:.2rem .3rem;border-bottom:1px solid rgba(255,255,255,.03)}
|
||||
code{font-family:ui-monospace,Menlo,monospace;font-size:.74rem;color:var(--text);word-break:break-all}
|
||||
ul{list-style:none;padding-left:.2rem}li{padding:.12rem 0;font-size:.82rem}li::before{content:"▸ ";color:var(--phos)}
|
||||
.pin h2,.pin{color:var(--amber)}
|
||||
.lvl{display:grid;grid-template-columns:repeat(3,1fr);gap:.4rem;margin-top:.4rem}
|
||||
.lvl button{padding:.55rem;cursor:pointer;font-family:inherit;font-size:.85rem;border-radius:8px;background:transparent;color:var(--text);border:1px solid var(--line)}
|
||||
.lvl button.on{border-width:2px;font-weight:700}
|
||||
.actions{display:flex;gap:.5rem;flex-wrap:wrap;justify-content:center;margin:1.2rem 0}
|
||||
.actions a{padding:.55rem 1rem;border:1px solid var(--phos);color:var(--phos-hot);text-decoration:none;border-radius:8px;font-size:.85rem}
|
||||
.footer{text-align:center;font-size:.66rem;color:var(--dim);margin-top:1.4rem;border-top:1px solid var(--line);padding-top:.7rem}
|
||||
.url{font-family:ui-monospace,monospace;font-size:.72rem;background:#0d0f15;padding:.12rem .35rem;border-radius:4px;margin:.1rem 0;display:block;word-break:break-all}
|
||||
</style></head>
|
||||
<body>
|
||||
|
||||
<h1>📡 GONDWANA TOOLBOX</h1>
|
||||
<p class="sub">// Rapport live — Cabine numérique VILLAGE3B</p>
|
||||
|
||||
{# Phase 3 (#492) : hero widgets — same shape as PDF #}
|
||||
{% set m = metrics or {} %}
|
||||
{% set sc = risk_score or 0 %}
|
||||
{% set rl = risk_label or 'LOW' %}
|
||||
<div class="card" style="background:linear-gradient(135deg,rgba(0,221,68,0.08),rgba(110,64,201,0.05));border-color:var(--phos)">
|
||||
<h2 style="display:flex;justify-content:space-between;align-items:center">
|
||||
📊 Ta session VILLAGE3B
|
||||
<span style="font-size:0.75rem;padding:0.3rem 0.8rem;border-radius:99px;
|
||||
background:{% if sc < 30 %}#00cc44{% elif sc < 70 %}#ffb347{% else %}#ff4466{% endif %};
|
||||
color:#0a0a0f;font-weight:bold">
|
||||
{% if sc < 30 %}🟢{% elif sc < 70 %}🟡{% else %}🔴{% endif %} {{ rl }} {{ sc }}/100
|
||||
</span>
|
||||
</h2>
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.5rem;margin-top:0.8rem">
|
||||
{% for emoji, val, lbl in [
|
||||
('🌐', m.connections|default(0), 'connexions'),
|
||||
('📡', m.unique_hosts|default(0), 'hôtes uniques'),
|
||||
('✅', m.successful|default(0), 'OK 2xx/3xx'),
|
||||
('🔒', m.tls_pinned|default(0), 'cert-pinning')
|
||||
] %}
|
||||
<div style="padding:0.6rem;background:rgba(0,0,0,0.3);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.2rem">{{ emoji }}</div>
|
||||
<div style="font-size:1.3rem;font-weight:bold;color:var(--phos-hot);text-shadow:0 0 4px var(--phos)">{{ val }}</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim)">{{ lbl }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:0.5rem;margin-top:0.5rem">
|
||||
{% set dpi_cls = dpi_classified or {} %}
|
||||
{% set cookies_p = cookies_providers or [] %}
|
||||
{% set geo_h = geo_top_hosts or [] %}
|
||||
{% set n_apps = (dpi_cls.top_apps|default([])|selectattr('app','ne','?')|list|length) %}
|
||||
{% set n_trackers = (cookies_p|map(attribute='count')|sum) %}
|
||||
{% set countries = geo_h|map(attribute='country')|reject('equalto','')|list %}
|
||||
{% set n_countries = countries|unique|list|length %}
|
||||
<div style="padding:0.6rem;background:rgba(25,25,45,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.2rem">📺</div>
|
||||
<div style="font-size:1.3rem;font-weight:bold;color:#9e76ff">{{ n_apps }}</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim)">apps détectées</div>
|
||||
</div>
|
||||
<div style="padding:0.6rem;background:rgba(45,25,35,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.2rem">🍪</div>
|
||||
<div style="font-size:1.3rem;font-weight:bold;color:#ff8866">{{ n_trackers }}</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim)">trackers</div>
|
||||
</div>
|
||||
<div style="padding:0.6rem;background:rgba(25,40,35,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.2rem">🌍</div>
|
||||
<div style="font-size:1.3rem;font-weight:bold;color:#66bbff">{{ n_countries }}</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim)">pays/ASN</div>
|
||||
</div>
|
||||
<div style="padding:0.6rem;background:rgba(60,50,15,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.2rem">{% if sc < 30 %}🟢{% elif sc < 70 %}🟡{% else %}🔴{% endif %}</div>
|
||||
<div style="font-size:1.3rem;font-weight:bold;color:{% if sc < 30 %}var(--phos-hot){% elif sc < 70 %}#ffb347{% else %}#ff4466{% endif %}">{{ sc }}</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim)">risque /100</div>
|
||||
</div>
|
||||
</div>
|
||||
{# 9th widget (poster sync #497) : 📱 empreinte device — full-width below #}
|
||||
{% set _avatar = avatar_analysis or {} %}
|
||||
{% set _dev_emj = _avatar.most_common_emoji or '❔' %}
|
||||
{% set _dev_label = _avatar.most_common or 'unknown' %}
|
||||
{% set _devices = _avatar.devices or {} %}
|
||||
{% set _dev_info = _devices.get(_dev_label, {}) %}
|
||||
{% set _os_label = _dev_info.os_label or _dev_label %}
|
||||
{% set _browsers = _avatar.browsers or {} %}
|
||||
<div style="margin-top:0.5rem;padding:0.7rem;background:rgba(45,25,60,0.5);border-radius:4px;text-align:center">
|
||||
<div style="font-size:1.4rem">📱 <b style="color:#cbb6ff">{{ _dev_emj }} Empreinte device</b></div>
|
||||
<div style="font-size:0.92rem;color:var(--phos-hot);margin-top:0.3rem;text-shadow:0 0 3px var(--phos)">
|
||||
{{ _os_label }}
|
||||
{% for br, info in _browsers.items() if br != 'unknown' %}
|
||||
· {{ info.emoji or '' }} {{ info.label or br }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div style="font-size:0.65rem;color:var(--dim);margin-top:0.2rem">{{ _avatar.raw_count or 0 }} UAs distincts observés</div>
|
||||
</div>
|
||||
{# Top device + app + ASN line #}
|
||||
{% set avatar = avatar_analysis or {} %}
|
||||
{% set top_dev = avatar.most_common or '?' %}
|
||||
{% set top_dev_emj = avatar.most_common_emoji or '' %}
|
||||
{% set top_app = (dpi_cls.top_apps and dpi_cls.top_apps[0]) or {} %}
|
||||
{% set top_geo = (geo_h and geo_h[0]) or {} %}
|
||||
{% set top_asn_raw = top_geo.asn_org if top_geo.asn_org else '?' %}
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-top:0.6rem;text-align:center;padding-top:0.5rem;border-top:1px solid var(--dim)">
|
||||
Top device : {{ top_dev_emj }} {{ top_dev }} ·
|
||||
Top app : {{ top_app.emoji|default('') }} {{ top_app.app|default('?') }} ·
|
||||
Top ASN : {{ top_geo.flag|default('') }} {{ top_asn_raw[:30] }}
|
||||
</p>
|
||||
</div>
|
||||
{% set sc = risk_score|default(0) %}
|
||||
{% set rl = risk_label|default('LOW') %}
|
||||
{% set ch = charts or {} %}
|
||||
{% set gcol = 'var(--phos-hot)' if sc < 30 else ('var(--amber)' if sc < 70 else 'var(--red)') %}
|
||||
{% set palette = ['#00dd44','#9e76ff','#ff8866','#66bbff','#ffb347','#ff4466'] %}
|
||||
{% set dpi_cls = dpi_classified or {} %}
|
||||
{% set cookies_p = cookies_providers or [] %}
|
||||
{% set geo_h = geo_top_hosts or [] %}
|
||||
{% set n_apps = (dpi_cls.top_apps|default([])|selectattr('app','ne','?')|list|length) %}
|
||||
{% set n_trackers = (cookies_p|map(attribute='count')|sum) %}
|
||||
{% set n_countries = (geo_h|map(attribute='country')|reject('equalto','')|list|unique|list|length) %}
|
||||
{% set _avatar = avatar_analysis or {} %}
|
||||
|
||||
{# Phase 3 (#492) : filtering compromissions visibility #}
|
||||
{% set t = transparency|default({}) %}
|
||||
{% set sens = t.get('sensitivity') %}
|
||||
<div class="card" style="background:rgba(255,68,102,0.06);border-color:var(--red)">
|
||||
<h2 style="color:var(--red)">🚨 Filtering compromissions actif</h2>
|
||||
<div class="kv" style="font-size:0.82rem">
|
||||
<span class="k">Sensibilité actuelle</span>
|
||||
<span class="v">{{ sens.label|default('—') if sens else '—' }}</span>
|
||||
<span class="k">Description</span>
|
||||
{% set sens_desc = (sens.description if sens and sens.description else 'Engine pas chargé') %}
|
||||
<span class="v" style="font-size:0.75rem">{{ sens_desc[:90] }}</span>
|
||||
<span class="k">🔍 Threat-intel feeds</span>
|
||||
<span class="v">{{ (threat_intel_matches|default([]))|length }} matches actifs</span>
|
||||
<span class="k">🎲 DGA candidates</span>
|
||||
<span class="v">{{ (dga_candidates|default([]))|length }} hôtes suspects</span>
|
||||
<span class="k">📡 Beaconing patterns</span>
|
||||
<span class="v">{{ (beaconing_candidates|default([]))|length }} détectés</span>
|
||||
<span class="k">🚫 Connexions bloquées</span>
|
||||
<span class="v">{{ t.get('attempts', {}).get('blocked', 0) }} <span style="font-size:0.7rem;color:var(--dim)">(Phase 4 : observation only)</span></span>
|
||||
</div>
|
||||
<p style="font-size:0.7rem;color:var(--dim);margin-top:0.5rem;font-style:italic">
|
||||
Le moteur détecte mais ne bloque pas encore — passive transparency. La règle engine
|
||||
décidera blocage actif quand R3 / Phase 4 sera wired.
|
||||
</p>
|
||||
</div>
|
||||
<h1>👁️ VILLAGE3B <span style="font-size:.8rem;color:var(--dim);font-weight:400">· mon rapport</span></h1>
|
||||
<p class="sub">Diagnostic live de ce que ton appareil envoie sur le réseau · anonyme · se rafraîchit tout seul</p>
|
||||
|
||||
{# Phase 3 (#492) : welcome / switch confirmation banner — uses server-side
|
||||
current_level (NOT request_args.level which is stale after refresh). #}
|
||||
{% if request_args and (request_args.get('welcome') or request_args.get('switched')) %}
|
||||
<div class="card" style="background:rgba(0,221,68,0.08);border-color:var(--phos)">
|
||||
<h2 style="color:var(--phos-hot)">
|
||||
{% if request_args.get('switched') %}🔄 Niveau changé{% else %}🎉 Bienvenue !{% endif %}
|
||||
</h2>
|
||||
<p style="font-size:0.85rem">
|
||||
Tu es maintenant en mode
|
||||
<b style="color:var(--phos-hot)">
|
||||
{% if current_level == 'r0' %}🌐 R0 — Bypass complet (aucune analyse)
|
||||
{% elif current_level == 'r2' %}🔍 R2 — Analyse + bandeau Safari
|
||||
{% else %}🛡 R1 — Analyse passive recommandée{% endif %}
|
||||
</b>.
|
||||
Tu peux maintenant <a href="http://captive.apple.com/hotspot-detect.html" target="_blank" style="color:var(--phos-hot);text-decoration:underline">surfer normalement</a> — ce rapport se met à jour toutes les 15s.
|
||||
</p>
|
||||
<div class="card" style="border-color:var(--phos)">
|
||||
<b style="color:var(--phos-hot)">{% if request_args.get('switched') %}🔄 Niveau changé{% else %}🎉 Bienvenue !{% endif %}</b> —
|
||||
tu es en mode
|
||||
{% if current_level == 'r0' %}🌐 R0 (aucune analyse){% elif current_level == 'r2' %}🔍 R2 (analyse + bandeau){% elif current_level == 'r3' %}🧅 R3 (tunnel){% else %}🛡 R1 (analyse passive){% endif %}.
|
||||
Tu peux surfer normalement.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Phase 3 (#492) : level switcher with active highlight from server-side
|
||||
current_level. Disables button if user clicks their own level (no-op). #}
|
||||
{# ── VERDICT HERO : score gauge + plain verdict ── #}
|
||||
<div class="card hero">
|
||||
<div class="gauge" style="background:conic-gradient({{ gcol }} {{ sc }}%, #1a1a22 {{ sc }}% 100%)">
|
||||
<div class="gauge-hole">
|
||||
<span class="gauge-num" style="color:{{ gcol }}">{{ sc }}</span>
|
||||
<span class="gauge-max">/ 100</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verdict" style="color:{{ gcol }}">
|
||||
{% if sc < 30 %}🟢 Tout va bien — {{ rl }}{% elif sc < 70 %}🟡 À surveiller — {{ rl }}{% else %}🔴 Attention — {{ rl }}{% endif %}
|
||||
</div>
|
||||
<p class="help">Score de risque de ton appareil. Plus il est <b>bas</b>, mieux tu es protégé.</p>
|
||||
{% if risk_explanation %}<p style="font-size:.85rem;margin-top:.5rem">{{ risk_explanation }}</p>{% endif %}
|
||||
</div>
|
||||
|
||||
{# ── KPI row ── #}
|
||||
<div class="kpis">
|
||||
<div class="kpi"><div class="e">🌐</div><div class="n">{{ m.connections|default(0) }}</div><div class="l">connexions</div></div>
|
||||
<div class="kpi"><div class="e">📡</div><div class="n">{{ m.unique_hosts|default(0) }}</div><div class="l">hôtes</div></div>
|
||||
<div class="kpi"><div class="e">🍪</div><div class="n">{{ n_trackers }}</div><div class="l">trackers</div></div>
|
||||
<div class="kpi"><div class="e">🌍</div><div class="n">{{ n_countries }}</div><div class="l">pays</div></div>
|
||||
<div class="kpi"><div class="e">📺</div><div class="n">{{ n_apps }}</div><div class="l">apps</div></div>
|
||||
<div class="kpi"><div class="e">🔒</div><div class="n">{{ m.tls_pinned|default(0) }}</div><div class="l">cert-pin</div></div>
|
||||
</div>
|
||||
<p class="help" style="text-align:center;margin-bottom:1rem">Ton appareil a contacté {{ m.unique_hosts|default(0) }} serveurs dans {{ n_countries }} pays, avec {{ n_trackers }} traceurs repérés.</p>
|
||||
|
||||
{# ── GRAPHS ── #}
|
||||
<div class="card">
|
||||
<h2>🔀 Mon niveau d'opt-in</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.5rem">
|
||||
📍 Tu es actuellement en
|
||||
<b style="color:{% if current_level == 'r0' %}var(--text){% elif current_level == 'r2' %}#ffd6a0{% else %}var(--phos-hot){% endif %}">
|
||||
{% if current_level == 'r0' %}🌐 R0 — Bypass complet
|
||||
{% elif current_level == 'r2' %}🔍 R2 — Analyse + bandeau
|
||||
{% else %}🛡 R1 — Analyse passive{% endif %}
|
||||
</b>
|
||||
· clique sur un autre niveau pour switcher
|
||||
</p>
|
||||
<form method="POST" action="/change-level" style="display:grid;grid-template-columns:repeat(3,1fr);gap:0.4rem">
|
||||
<button type="submit" name="level" value="r0"
|
||||
style="padding:0.5rem;cursor:pointer;font-family:inherit;font-size:0.85rem;
|
||||
{% if current_level == 'r0' %}
|
||||
background:rgba(255,255,255,0.08);color:var(--text);border:2px solid var(--text);font-weight:bold;
|
||||
{% else %}
|
||||
background:transparent;color:var(--text);border:1px solid var(--dim);
|
||||
{% endif %}">
|
||||
{% if current_level == 'r0' %}✓ {% endif %}🌐 R0
|
||||
</button>
|
||||
<button type="submit" name="level" value="r1"
|
||||
style="padding:0.5rem;cursor:pointer;font-family:inherit;font-size:0.85rem;
|
||||
{% if current_level == 'r1' %}
|
||||
background:rgba(0,221,68,0.25);color:var(--phos-hot);border:2px solid var(--phos);font-weight:bold;
|
||||
{% else %}
|
||||
background:transparent;color:var(--phos);border:1px solid var(--dim);
|
||||
{% endif %}">
|
||||
{% if current_level == 'r1' %}✓ {% endif %}🛡 R1
|
||||
</button>
|
||||
<button type="submit" name="level" value="r2"
|
||||
style="padding:0.5rem;cursor:pointer;font-family:inherit;font-size:0.85rem;
|
||||
{% if current_level == 'r2' %}
|
||||
background:rgba(255,179,71,0.25);color:#ffd6a0;border:2px solid #ffb347;font-weight:bold;
|
||||
{% else %}
|
||||
background:transparent;color:#9d7846;border:1px solid var(--dim);
|
||||
{% endif %}">
|
||||
{% if current_level == 'r2' %}✓ {% endif %}🔍 R2
|
||||
</button>
|
||||
<h2>📊 En un coup d'œil</h2>
|
||||
<div class="graphs">
|
||||
|
||||
{# trackers donut #}
|
||||
<div>
|
||||
<div style="font-size:.82rem;color:var(--dim);margin-bottom:.4rem">🍪 Qui te trace</div>
|
||||
{% if ch.trackers %}
|
||||
<div class="donut-wrap">
|
||||
<div class="donut" style="background:conic-gradient({% for t in ch.trackers %}{{ palette[loop.index0 % palette|length] }} {{ t.start }}% {{ t.end }}%{% if not loop.last %},{% endif %}{% endfor %})">
|
||||
<div class="donut-hole">{{ n_trackers }}<br>traceurs</div>
|
||||
</div>
|
||||
<div class="legend">
|
||||
{% for t in ch.trackers %}
|
||||
<span class="row"><span class="dot" style="background:{{ palette[loop.index0 % palette|length] }}"></span>{{ t.emoji }} {{ t.label[:14] }} <b style="color:var(--text)">{{ t.pct }}%</b></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}<div class="empty">Aucun traceur repéré 🎉</div>{% endif %}
|
||||
</div>
|
||||
|
||||
{# countries bars #}
|
||||
<div>
|
||||
<div style="font-size:.82rem;color:var(--dim);margin-bottom:.4rem">🌍 Vers quels pays</div>
|
||||
{% if ch.countries %}
|
||||
{% for c in ch.countries %}
|
||||
<div class="bar-row"><span class="bar-lbl">{{ c.flag }} {{ c.label[:8] }}</span><span class="bar-track"><span class="bar-fill" style="width:{{ c.pct }}%"></span></span><span class="bar-val">{{ c.count }}</span></div>
|
||||
{% endfor %}
|
||||
{% else %}<div class="empty">Pas encore de données géo</div>{% endif %}
|
||||
</div>
|
||||
|
||||
{# apps bars #}
|
||||
<div style="grid-column:1/-1">
|
||||
<div style="font-size:.82rem;color:var(--dim);margin-bottom:.4rem">📺 Quelles apps / services</div>
|
||||
{% if ch.apps %}
|
||||
{% for a in ch.apps %}
|
||||
<div class="bar-row"><span class="bar-lbl">{{ a.emoji }} {{ a.label[:16] }}</span><span class="bar-track"><span class="bar-fill" style="width:{{ a.pct }}%;background:linear-gradient(90deg,var(--violet),#c9b6ff)"></span></span><span class="bar-val" style="color:var(--violet)">{{ a.count }}</span></div>
|
||||
{% endfor %}
|
||||
{% else %}<div class="empty">Aucune app classifiée</div>{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<p class="help">Les traceurs suivent ta navigation entre sites. Les apps cert-pinning (🔒) refusent l'analyse — c'est bon signe.</p>
|
||||
</div>
|
||||
|
||||
{# ── LEVEL SWITCHER (action) ── #}
|
||||
<div class="card">
|
||||
<h2>🔀 Mon niveau de protection</h2>
|
||||
<form method="POST" action="/change-level" class="lvl">
|
||||
<button type="submit" name="level" value="r0" class="{{ 'on' if current_level=='r0' }}" style="{% if current_level=='r0' %}border-color:var(--text){% endif %}">{% if current_level=='r0' %}✓ {% endif %}🌐 R0</button>
|
||||
<button type="submit" name="level" value="r1" class="{{ 'on' if current_level=='r1' }}" style="{% if current_level=='r1' %}border-color:var(--phos);color:var(--phos-hot){% endif %}">{% if current_level=='r1' %}✓ {% endif %}🛡 R1</button>
|
||||
<button type="submit" name="level" value="r2" class="{{ 'on' if current_level=='r2' }}" style="{% if current_level=='r2' %}border-color:var(--amber);color:var(--amber){% endif %}">{% if current_level=='r2' %}✓ {% endif %}🔍 R2</button>
|
||||
</form>
|
||||
{% if wg_enabled|default(false) %}
|
||||
{# Phase 6 (#496) : R3 WireGuard separate action (different flow — install profile first) #}
|
||||
<p style="font-size:0.7rem;color:var(--dim);margin-top:0.5rem;text-align:center">
|
||||
Pour le mode R3 WireGuard (mobile + tout-décrypté) :
|
||||
<a href="/wg/r3-install" style="color:#9e76ff;text-decoration:underline">🌐 installer le profil</a>
|
||||
</p>
|
||||
{% if current_level == 'r3' %}
|
||||
<p style="font-size:0.72rem;color:#9e76ff;margin-top:0.3rem;text-align:center;font-weight:bold">
|
||||
✓ Tu es actuellement en mode R3 — tunnel WG actif
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p class="help">R0 = aucune analyse · R1 = analyse passive (recommandé) · R2 = analyse + bandeau.{% if wg_enabled|default(false) %} <a href="/wg/r3-install" style="color:var(--violet)">🧅 R3 tunnel mobile</a>.{% endif %}</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>👤 Identifiant anonyme</h2>
|
||||
<div class="kv">
|
||||
<span class="k">Hash session</span> <span class="v">{{ mac_hash }}</span>
|
||||
<span class="k">Sandbox IP</span> <span class="v">{{ ip }}</span>
|
||||
<span class="k">Appareil</span> <span class="v">{{ device_type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{# ════════════ DÉTAILS TECHNIQUES (repliés) ════════════ #}
|
||||
|
||||
<div class="card">
|
||||
<h2>📊 Métriques session</h2>
|
||||
<div class="kv">
|
||||
<span class="k">Connexions</span> <span class="v">{{ metrics.connections }}</span>
|
||||
<span class="k">Hosts uniques</span> <span class="v">{{ metrics.unique_hosts }}</span>
|
||||
<span class="k">Réussies</span> <span class="v">{{ metrics.successful }}</span>
|
||||
<span class="k">Cert-pin block</span> <span class="v">{{ metrics.tls_pinned }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>🎯 Analyse compromission</h2>
|
||||
<p style="text-align:center;margin:0.5rem 0">
|
||||
{% if risk_score < 30 %}
|
||||
<span class="score low">Score : {{ risk_score }}/100 — {{ risk_label|default('LOW') }}</span>
|
||||
{% elif risk_score < 70 %}
|
||||
<span class="score med">Score : {{ risk_score }}/100 — {{ risk_label|default('MEDIUM') }}</span>
|
||||
{% else %}
|
||||
<span class="score high">Score : {{ risk_score }}/100 — {{ risk_label|default('HIGH') }}</span>
|
||||
<details>
|
||||
<summary>🎯 Analyse de compromission & score</summary>
|
||||
<div class="inner">
|
||||
{% if risk_explanation %}<p style="font-size:.85rem;margin-bottom:.6rem">{{ risk_explanation }}</p>{% endif %}
|
||||
<ul>{% for ind in indicators %}<li>{{ ind }}</li>{% endfor %}</ul>
|
||||
{% if scoring and scoring.breakdown %}
|
||||
<table style="margin-top:.6rem"><thead><tr><th>Catégorie</th><th style="text-align:right">Signaux</th><th style="text-align:right">Poids</th></tr></thead><tbody>
|
||||
{% for b in scoring.breakdown %}
|
||||
<tr><td>{{ b.category }}</td><td style="text-align:right">{{ b.raw_signal_count }}</td><td style="text-align:right;color:var(--amber)">+{{ b.weight_subtotal }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if risk_explanation %}
|
||||
<p style="font-size:0.85rem;color:var(--text);margin-bottom:0.8rem;padding:0.5rem;background:rgba(0,221,68,0.05);border-left:2px solid var(--phos)">{{ risk_explanation }}</p>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for ind in indicators %}<li>{{ ind }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{% if scoring and scoring.breakdown %}
|
||||
<div class="card">
|
||||
<h2>🔬 Breakdown du score (transparent)</h2>
|
||||
<table style="width:100%;font-size:0.82rem;border-collapse:collapse">
|
||||
<thead><tr style="color:var(--dim);border-bottom:1px solid var(--dim)">
|
||||
<th style="padding:0.2rem 0.4rem;text-align:left">Catégorie</th>
|
||||
<th style="padding:0.2rem 0.4rem;text-align:right">Signaux</th>
|
||||
<th style="padding:0.2rem 0.4rem;text-align:right">Poids</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for b in scoring.breakdown %}
|
||||
<tr><td style="padding:0.15rem 0.4rem">{{ b.category }}</td>
|
||||
<td style="padding:0.15rem 0.4rem;text-align:right">{{ b.raw_signal_count }}</td>
|
||||
<td style="padding:0.15rem 0.4rem;text-align:right;color:var(--amber);text-shadow:0 0 4px var(--amber)">+{{ b.weight_subtotal }}</td></tr>
|
||||
{% if b.examples %}
|
||||
<tr><td colspan="3" style="padding:0;font-size:0.75rem;color:var(--text)">
|
||||
{% for ex in b.examples[:3] %}<div style="margin-left:1rem;padding:0.1rem 0">▸ <code>{{ ex }}</code></div>{% endfor %}
|
||||
</td></tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if threat_intel_matches %}
|
||||
<div class="card">
|
||||
<h2 class="pin">🚨 Threat-intel : matches feeds malware</h2>
|
||||
<ul>
|
||||
{% for m in threat_intel_matches[:10] %}
|
||||
<li>{{ m.flag }} <span style="color:var(--red)">[{{ m.source }}/{{ m.weight }}]</span> <b>{{ m.label }}</b> : <code>{{ m.ioc[:60] }}</code>{% if m.asn_org %} <span style="font-size:0.75rem;color:var(--dim)">({{ m.asn_org }})</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if dga_candidates %}
|
||||
<div class="card">
|
||||
<h2 class="pin">🔠 DGA — domaines suspects</h2>
|
||||
<ul>
|
||||
{% for d in dga_candidates[:8] %}
|
||||
<li>{{ d.flag }} <span style="color:var(--amber)">[{{ d.score }}]</span> <code>{{ d.host[:60] }}</code> <span style="font-size:0.75rem;color:var(--dim)">({{ d.indicators|join(', ') }})</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if beaconing_candidates %}
|
||||
<div class="card">
|
||||
<h2 class="pin">📡 Beaconing — patterns périodiques suspects</h2>
|
||||
<table style="width:100%;font-size:0.82rem">
|
||||
<thead><tr style="color:var(--dim);text-align:left">
|
||||
<th>🚩</th><th>Score</th><th>Host</th><th style="text-align:right">Median</th><th style="text-align:right">CV</th><th>ASN</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for b in beaconing_candidates[:8] %}
|
||||
<tr><td>{{ b.flag }}</td><td style="color:var(--amber)">{{ b.score }}</td><td><code>{{ b.host[:40] }}</code></td>
|
||||
<td style="text-align:right">{{ b.median_seconds }}s</td>
|
||||
<td style="text-align:right">{{ b.cv }}</td>
|
||||
<td style="font-size:0.78rem;color:var(--dim)">{{ b.asn_org[:30] if b.asn_org else '' }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if dpi_classified and dpi_classified.top_apps %}
|
||||
<div class="card">
|
||||
<h2>🧭 Apps détectées (classification nDPI-style)</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.5rem">
|
||||
Catégories : {% for cat, n in dpi_classified.by_category.items() %}{{ dpi_classified.category_emoji.get(cat, '❔') }} {{ cat }}({{ n }}){% if not loop.last %} · {% endif %}{% endfor %}
|
||||
</p>
|
||||
<table style="width:100%;font-size:0.85rem">
|
||||
<thead><tr style="color:var(--dim);text-align:left">
|
||||
<th>App</th><th>Catégorie</th><th style="text-align:right">Connexions</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for a in dpi_classified.top_apps[:15] %}
|
||||
<tr><td>{{ a.emoji }} <b>{{ a.app }}</b></td><td style="font-size:0.78rem;color:var(--dim)">{{ a.category }}</td>
|
||||
<td style="text-align:right;color:var(--phos);text-shadow:0 0 4px var(--phos)">{{ a.count }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if threat_intel_matches or dga_candidates or beaconing_candidates %}
|
||||
<details>
|
||||
<summary class="pin">🚨 Menaces détectées ({{ (threat_intel_matches|default([]))|length + (dga_candidates|default([]))|length + (beaconing_candidates|default([]))|length }})</summary>
|
||||
<div class="inner">
|
||||
{% if threat_intel_matches %}<p style="color:var(--amber);font-size:.82rem;margin:.3rem 0">🚨 Threat-intel (feeds malware)</p><ul>
|
||||
{% for x in threat_intel_matches[:10] %}<li>{{ x.flag }} <span style="color:var(--red)">[{{ x.source }}]</span> <b>{{ x.label }}</b> <code>{{ x.ioc[:50] }}</code></li>{% endfor %}</ul>{% endif %}
|
||||
{% if dga_candidates %}<p style="color:var(--amber);font-size:.82rem;margin:.5rem 0 .2rem">🔠 Domaines générés (DGA)</p><ul>
|
||||
{% for d in dga_candidates[:8] %}<li>{{ d.flag }} [{{ d.score }}] <code>{{ d.host[:50] }}</code></li>{% endfor %}</ul>{% endif %}
|
||||
{% if beaconing_candidates %}<p style="color:var(--amber);font-size:.82rem;margin:.5rem 0 .2rem">📡 Beaconing (périodique)</p><ul>
|
||||
{% for b in beaconing_candidates[:8] %}<li>{{ b.flag }} [{{ b.score }}] <code>{{ b.host[:40] }}</code> · {{ b.median_seconds }}s</li>{% endfor %}</ul>{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if geo_top_hosts %}
|
||||
<div class="card">
|
||||
<h2>🌍 Hôtes contactés (par pays + ASN + app)</h2>
|
||||
<table style="width:100%;font-size:0.82rem">
|
||||
<thead><tr style="color:var(--dim);text-align:left">
|
||||
<th>🚩</th><th>App</th><th>Hôte</th><th>ASN</th><th style="text-align:right">Hits</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for h in geo_top_hosts[:15] %}
|
||||
<tr><td>{{ h.flag }}</td><td>{{ h.emoji }} {{ h.app[:18] }}</td><td><code style="font-size:0.78rem">{{ h.host[:45] }}</code></td>
|
||||
<td style="font-size:0.75rem;color:var(--dim)">{{ h.asn_org[:25] if h.asn_org else '' }}</td>
|
||||
<td style="text-align:right;color:var(--phos)">{{ h.count }}</td></tr>
|
||||
<details>
|
||||
<summary>🌍 Hôtes contactés ({{ geo_top_hosts|length }})</summary>
|
||||
<div class="inner"><table><thead><tr><th>🚩</th><th>App</th><th>Hôte</th><th style="text-align:right">Hits</th></tr></thead><tbody>
|
||||
{% for h in geo_top_hosts[:20] %}
|
||||
<tr><td>{{ h.flag }}</td><td>{{ h.emoji }} {{ h.app[:14] }}</td><td><code>{{ h.host[:42] }}</code></td><td style="text-align:right;color:var(--phos)">{{ h.count }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</tbody></table></div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if avatar_analysis and avatar_analysis.devices %}
|
||||
<div class="card">
|
||||
<h2>{{ avatar_analysis.most_common_emoji }} Avatar / device fingerprint</h2>
|
||||
<p style="margin-bottom:0.5rem">
|
||||
<span style="font-size:1.2rem">{{ avatar_analysis.most_common_emoji }}</span>
|
||||
<b>{{ avatar_analysis.most_common }}</b> (le plus représenté)
|
||||
</p>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:1rem;font-size:0.85rem">
|
||||
<div>
|
||||
<div style="color:var(--dim);font-size:0.78rem;margin-bottom:0.3rem">Devices :</div>
|
||||
{% for dev, info in avatar_analysis.devices.items() %}
|
||||
<div>{{ info.emoji }} <b>{{ info.os_label }}</b> <span style="color:var(--dim)">({{ info.count }})</span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<div style="color:var(--dim);font-size:0.78rem;margin-bottom:0.3rem">Browsers :</div>
|
||||
{% for br, info in avatar_analysis.browsers.items() %}
|
||||
<div>{{ info.emoji }} <b>{{ info.label }}</b> <span style="color:var(--dim)">({{ info.count }})</span></div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if dpi_classified and dpi_classified.top_apps %}
|
||||
<details>
|
||||
<summary>🧭 Apps détectées (nDPI)</summary>
|
||||
<div class="inner"><table><thead><tr><th>App</th><th>Catégorie</th><th style="text-align:right">Conn.</th></tr></thead><tbody>
|
||||
{% for a in dpi_classified.top_apps[:20] %}
|
||||
<tr><td>{{ a.emoji }} <b>{{ a.app }}</b></td><td style="color:var(--dim)">{{ a.category }}</td><td style="text-align:right;color:var(--phos)">{{ a.count }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody></table></div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if cookies_providers %}
|
||||
<div class="card">
|
||||
<h2>🍪 Trackers / providers cookies (Phase 2a+)</h2>
|
||||
<ul>
|
||||
{% for p in cookies_providers[:10] %}
|
||||
<li>{{ p.emoji }} <b>{{ p.provider }}</b> <span style="font-size:0.78rem;color:var(--dim)">({{ p.category }})</span> <span style="color:var(--phos)">×{{ p.count }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<details>
|
||||
<summary>🍪 Traceurs / providers cookies ({{ cookies_providers|length }})</summary>
|
||||
<div class="inner"><ul>
|
||||
{% for p in cookies_providers[:15] %}<li>{{ p.emoji }} <b>{{ p.provider }}</b> <span style="color:var(--dim)">({{ p.category }})</span> <span style="color:var(--phos)">×{{ p.count }}</span></li>{% endfor %}
|
||||
</ul></div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<h2>📱 Apps détectées (vue IP forensics)</h2>
|
||||
<ul>
|
||||
{% for app in apps_detected %}<li>{{ app }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2 class="pin">🔒 Apps protégées par cert-pinning</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.5rem">
|
||||
Ces apps refusent notre certificat — ToolBoX ne peut PAS lire leur contenu. C'est un BON signe sécurité.
|
||||
</p>
|
||||
<ul>
|
||||
{% for app in pinned_apps %}<li>{{ app }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if dpi and dpi.top_hosts %}
|
||||
<div class="card">
|
||||
<h2>🔍 DPI — hôtes les plus contactés</h2>
|
||||
<table style="width:100%;font-size:0.82rem;border-collapse:collapse">
|
||||
<thead><tr style="color:var(--dim);text-align:left;border-bottom:1px solid var(--dim)">
|
||||
<th style="padding:0.2rem 0.4rem">Hôte</th><th style="padding:0.2rem 0.4rem;text-align:right">Requêtes</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for entry in dpi.top_hosts[:10] %}
|
||||
<tr><td style="padding:0.15rem 0.4rem;font-family:monospace">{{ entry.host[:70] }}</td>
|
||||
<td style="padding:0.15rem 0.4rem;text-align:right;color:var(--phos);text-shadow:0 0 4px var(--phos)">{{ entry.count }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if dpi.methods %}
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-top:0.6rem">
|
||||
Méthodes : {% for m,c in dpi.methods.items() %}<span style="color:var(--phos)">{{ m }}({{ c }})</span>{% if not loop.last %} · {% endif %}{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if dpi.user_agents %}
|
||||
<p style="font-size:0.75rem;color:var(--dim);margin-top:0.4rem">
|
||||
UA détectés : {{ dpi.user_agents|length }}
|
||||
{% if dpi.user_agents %}<br><span style="color:var(--text);font-family:monospace;font-size:0.7rem">{{ dpi.user_agents[0][:90] }}</span>{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if cookies and (cookies.total_set or cookies.details) %}
|
||||
<div class="card">
|
||||
<h2>🍪 Cookies / trackers</h2>
|
||||
<div class="kv" style="margin-bottom:0.5rem">
|
||||
<span class="k">Set-Cookie reçus</span> <span class="v">{{ cookies.total_set }}</span>
|
||||
<span class="k">Cookies envoyés</span> <span class="v">{{ cookies.total_sent }}</span>
|
||||
</div>
|
||||
{% if cookies.details %}
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-top:0.4rem">URLs avec activité cookies (top {{ cookies.details|length }}) :</p>
|
||||
{% for d in cookies.details[:10] %}
|
||||
<div class="url">
|
||||
<span style="color:var(--amber)">set={{ d.set }}/sent={{ d.sent }}</span> · {{ d.url[:90] }}
|
||||
{% if avatar_analysis and avatar_analysis.devices %}
|
||||
<details>
|
||||
<summary>{{ _avatar.most_common_emoji }} Empreinte appareil</summary>
|
||||
<div class="inner">
|
||||
<p style="margin-bottom:.4rem">{{ _avatar.most_common_emoji }} <b>{{ _avatar.most_common }}</b> · {{ _avatar.raw_count|default(0) }} UAs distincts</p>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:1.2rem;font-size:.84rem">
|
||||
<div><div style="color:var(--dim);font-size:.75rem">Devices</div>{% for dev,info in _avatar.devices.items() %}<div>{{ info.emoji }} {{ info.os_label }} ({{ info.count }})</div>{% endfor %}</div>
|
||||
<div><div style="color:var(--dim);font-size:.75rem">Browsers</div>{% for br,info in (_avatar.browsers or {}).items() %}<div>{{ info.emoji }} {{ info.label }} ({{ info.count }})</div>{% endfor %}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if soc and soc.indicators %}
|
||||
<div class="card">
|
||||
<h2 class="pin">⚠ SOC — indicateurs détectés</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.5rem">
|
||||
Score SOC actuel : <b style="color:var(--amber)">{{ soc.score }}/100</b>
|
||||
</p>
|
||||
<ul>
|
||||
{% for ind in soc.indicators[:10] %}
|
||||
<li><span style="color:var(--amber)">[poids {{ ind.weight }}]</span> {{ ind.kind }} : <code>{{ ind.host[:60] }}</code></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if pinned_apps %}
|
||||
<details>
|
||||
<summary>🔒 Apps protégées (cert-pinning) ({{ pinned_apps|length }})</summary>
|
||||
<div class="inner"><p class="help" style="margin-bottom:.4rem">Ces apps refusent notre certificat — on ne peut PAS lire leur contenu. Bon signe.</p>
|
||||
<ul>{% for app in pinned_apps %}<li>{{ app }}</li>{% endfor %}</ul></div>
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
{% if ja4 and ja4.snis_seen %}
|
||||
<div class="card">
|
||||
<h2>🔐 JA4 — empreintes TLS</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.4rem">
|
||||
SNIs vus (top {{ ja4.snis_seen|length }}) :
|
||||
</p>
|
||||
{% for sni in ja4.snis_seen[:8] %}
|
||||
<div class="url">{{ sni }}</div>
|
||||
{% endfor %}
|
||||
{% if ja4.alpns_seen %}
|
||||
<p style="font-size:0.75rem;color:var(--dim);margin-top:0.5rem">
|
||||
ALPN protocols : <span style="color:var(--phos)">{{ ja4.alpns_seen|join(', ') }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if inspected_urls and not (cookies and cookies.details) %}
|
||||
<div class="card">
|
||||
<h2>👁 Trafic inspecté (R2 consent explicite)</h2>
|
||||
{% for url in inspected_urls[:15] %}
|
||||
<div class="url">{{ url }}</div>
|
||||
{% endfor %}
|
||||
{% if inspected_urls|length > 15 %}
|
||||
<p style="font-size:0.75rem;color:var(--dim);margin-top:0.4rem">… et {{ inspected_urls|length - 15 }} autres URLs</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<h2>✅ Recommandations</h2>
|
||||
<ul>
|
||||
{% for rec in recommendations %}<li>{{ rec }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
{# Phase 6 (#496) : pass mh= so R3 WG clients can download from kbin too #}
|
||||
<a href="/report/me?mh={{ mac_hash }}">⬇ Télécharger PDF</a>
|
||||
<a href="/">↩ Retour splash</a>
|
||||
<a href="/status">📊 Statut JSON</a>
|
||||
</div>
|
||||
|
||||
{# Phase 3 (#492) : transparency layer — inspection breakdown + per-host quality #}
|
||||
{% set t = transparency|default({}) %}
|
||||
{% if t and t.get('total_events', 0) > 0 %}
|
||||
<div class="card">
|
||||
<h2>🔎 INSPECTION : CE QU'ON A REGARDÉ (et ce qu'on n'a PAS regardé)</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.6rem;font-style:italic">
|
||||
Honnêteté avant magie : la cabine te dit ce qu'elle a inspecté, ce qu'elle a sciemment bypassé, et pourquoi.
|
||||
</p>
|
||||
<div class="kv">
|
||||
{% set b = t.get('breakdown_pct', {}) %}
|
||||
{% if b.get('inspected') %}
|
||||
<div class="k">🔍 Inspecté (HTTPS via notre CA)</div>
|
||||
<div class="v">{{ b.get('inspected', 0) }}% — contenu visible</div>
|
||||
{% endif %}
|
||||
{% if b.get('bypassed-whitelist') %}
|
||||
<div class="k">🛡 Bypass whitelist</div>
|
||||
<div class="v">{{ b.get('bypassed-whitelist', 0) }}% — décision policy (cert-pinning vendor)</div>
|
||||
{% endif %}
|
||||
{% if b.get('pinned-failed-mitm') %}
|
||||
<div class="k">🔒 Cert-pinning détecté</div>
|
||||
<div class="v">{{ b.get('pinned-failed-mitm', 0) }}% — l'app refuse notre CA, normal+bon signe</div>
|
||||
{% endif %}
|
||||
{% if b.get('e2e-opaque') %}
|
||||
<div class="k">🔐 E2E messaging</div>
|
||||
<div class="v">{{ b.get('e2e-opaque', 0) }}% — opaque par design, ton chiffrement marche</div>
|
||||
{% endif %}
|
||||
<div class="k">📊 Total events analysés</div>
|
||||
<div class="v">{{ t.get('total_events', 0) }}</div>
|
||||
{% if t.get('whitelist_stats') %}
|
||||
<div class="k">📜 Patterns whitelist actifs</div>
|
||||
<div class="v">{{ t.get('whitelist_stats', {}).get('count', 0) }} (baseline + override)</div>
|
||||
{% endif %}
|
||||
{% if t.get('sensitivity') %}
|
||||
<div class="k">🎛 Sensibilité active</div>
|
||||
<div class="v">{{ t.get('sensitivity', {}).get('label', '?') }} — {{ t.get('sensitivity', {}).get('description', '')[:80] }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Phase 3 (#492) : tentatives counters — full transparency #}
|
||||
{% set a = t.get('attempts', {}) %}
|
||||
{% if a.get('total', 0) > 0 %}
|
||||
<div style="margin-top:0.8rem;padding-top:0.6rem;border-top:1px solid var(--dim)">
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.4rem"><b>📈 Tentatives observées (toutes catégories)</b></p>
|
||||
<div class="kv" style="font-size:0.8rem">
|
||||
<span class="k">Total observé</span><span class="v">{{ a.get('total', 0) }}</span>
|
||||
<span class="k">🔍 Inspecté</span><span class="v">{{ a.get('inspected', 0) }}</span>
|
||||
<span class="k">🛡 Bypass</span><span class="v">{{ a.get('bypassed_whitelist', 0) }}</span>
|
||||
<span class="k">🔒 Cert-pinning</span><span class="v">{{ a.get('pinned_failed', 0) }}</span>
|
||||
<span class="k">🔐 E2E opaque</span><span class="v">{{ a.get('e2e_opaque', 0) }}</span>
|
||||
{% if a.get('blocked', 0) > 0 %}
|
||||
<span class="k">🚫 Bloqué</span><span class="v" style="color:var(--red)">{{ a.get('blocked', 0) }}</span>
|
||||
{% endif %}
|
||||
<details>
|
||||
<summary>🔎 Transparence : ce qu'on a regardé</summary>
|
||||
<div class="inner">
|
||||
<p class="help" style="margin-bottom:.5rem">Honnêteté avant magie : ce qu'on a inspecté, bypassé, et pourquoi.</p>
|
||||
<div class="kv">
|
||||
{% set b = t.get('breakdown_pct', {}) %}
|
||||
{% if b.get('inspected') %}<span class="k">🔍 Inspecté</span><span class="v">{{ b.get('inspected') }}%</span>{% endif %}
|
||||
{% if b.get('pinned-failed-mitm') %}<span class="k">🔒 Cert-pinning</span><span class="v">{{ b.get('pinned-failed-mitm') }}%</span>{% endif %}
|
||||
{% if b.get('e2e-opaque') %}<span class="k">🔐 E2E chiffré</span><span class="v">{{ b.get('e2e-opaque') }}%</span>{% endif %}
|
||||
<span class="k">📊 Total events</span><span class="v">{{ t.get('total_events', 0) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Phase 3 (#492) : whitelist hits — accountability per pattern/category #}
|
||||
{% set wh = t.get('whitelist_hits', {}) %}
|
||||
{% if wh.get('total', 0) > 0 %}
|
||||
<div style="margin-top:0.8rem;padding-top:0.6rem;border-top:1px solid var(--dim)">
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.4rem"><b>📜 Tes bypass whitelist en détail</b> — {{ wh.get('total') }} connexions sciemment non-inspectées</p>
|
||||
<div class="kv" style="font-size:0.78rem">
|
||||
{% for cat, cnt in (wh.get('by_category', {}) | dictsort(by='value', reverse=True))[:8] %}
|
||||
<span class="k">{{ cat }}</span><span class="v">{{ cnt }} hits</span>
|
||||
{% if t.get('per_host') %}
|
||||
<table style="margin-top:.7rem"><thead><tr><th>Grade</th><th>Destination</th><th>Statut</th></tr></thead><tbody>
|
||||
{% for h in t.get('per_host', [])[:15] %}
|
||||
<tr><td style="font-weight:700;color:{% if h.grade in ['A+','A'] %}var(--phos-hot){% elif h.grade=='B' %}var(--phos){% elif h.grade=='C' %}var(--amber){% else %}var(--red){% endif %}">{{ h.grade }}</td><td><code>{{ h.host[:48] }}</code></td><td style="color:var(--dim);font-size:.72rem">{{ h.status }}</td></tr>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<ul style="margin-top:0.4rem">
|
||||
{% for p in wh.get('top_patterns', [])[:8] %}
|
||||
<li style="font-size:0.72rem"><code>{{ p.pattern }}</code> · {{ p.count }} hits</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</tbody></table>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if t.get('per_host') %}
|
||||
<div class="card">
|
||||
<h2>🎯 QUALITÉ SÉCURITÉ PAR DESTINATION</h2>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-bottom:0.6rem;font-style:italic">
|
||||
Grade A+/A/B/C/D/F basé sur TLS version + JA4 + headers + cookies. Worst-first :
|
||||
</p>
|
||||
<table style="width:100%;font-size:0.78rem;border-collapse:collapse">
|
||||
<thead>
|
||||
<tr style="color:var(--phos);text-align:left;border-bottom:1px solid var(--dim)">
|
||||
<th style="padding:0.3rem">Grade</th>
|
||||
<th style="padding:0.3rem">Destination</th>
|
||||
<th style="padding:0.3rem">Statut analyse</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for h in t.get('per_host', [])[:15] %}
|
||||
<tr style="border-bottom:1px solid rgba(0,221,68,0.1)">
|
||||
<td style="padding:0.25rem;font-weight:bold;color:{% if h.grade in ['A+','A'] %}var(--phos-hot){% elif h.grade == 'B' %}var(--phos){% elif h.grade == 'C' %}var(--amber){% else %}var(--red){% endif %}">{{ h.grade }}</td>
|
||||
<td style="padding:0.25rem;font-family:monospace;color:var(--text)">{{ h.host[:60] }}</td>
|
||||
<td style="padding:0.25rem;color:var(--dim);font-size:0.72rem">{{ h.status }} — {{ h.reason[:70] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
<p class="refresh">↻ Auto-refresh toutes les 15 secondes</p>
|
||||
<details>
|
||||
<summary>👤 Identité & recommandations</summary>
|
||||
<div class="inner">
|
||||
<div class="kv">
|
||||
<span class="k">Hash session</span><span class="v"><code>{{ mac_hash }}</code></span>
|
||||
<span class="k">Sandbox IP</span><span class="v">{{ ip }}</span>
|
||||
<span class="k">Appareil</span><span class="v">{{ device_type }}</span>
|
||||
</div>
|
||||
{% if recommendations %}<p style="color:var(--phos-hot);font-size:.85rem;margin:.6rem 0 .2rem">✅ Recommandations</p><ul>{% for rec in recommendations %}<li>{{ rec }}</li>{% endfor %}</ul>{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div class="actions">
|
||||
<a href="/report/me?mh={{ mac_hash }}">⬇ Télécharger le PDF</a>
|
||||
<a href="/social/me?mh={{ mac_hash }}">🕸️ Ma carto</a>
|
||||
<a href="/landing">↩ Accueil</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>💚 Support & soutien projet</h2>
|
||||
<p style="font-size:0.82rem;color:var(--text);line-height:1.55">
|
||||
Gondwana ToolBoX est un <b>commun numérique</b> open-source maintenu par
|
||||
CyberMind / Gérald Kerma (Notre-Dame-du-Cruet, Savoie). Pas de pub, pas de
|
||||
revente, pas de tracking commercial. Si ce service t'a aidé :
|
||||
</p>
|
||||
<ul style="margin-top:0.5rem">
|
||||
<li>💰 <b>Don récurrent</b> : <a href="https://liberapay.com/cybermind" style="color:var(--phos);text-decoration:underline">liberapay.com/cybermind</a></li>
|
||||
<li>💳 <b>Don ponctuel</b> : <a href="https://cybermind.fr/don" style="color:var(--phos);text-decoration:underline">cybermind.fr/don</a> (carte, virement SEPA)</li>
|
||||
<li>📧 <b>Support technique</b> : <a href="mailto:support@cybermind.fr" style="color:var(--phos);text-decoration:underline">support@cybermind.fr</a></li>
|
||||
<li>🐛 <b>Signaler un bug</b> : <a href="https://github.com/CyberMind-FR/secubox-deb/issues" style="color:var(--phos);text-decoration:underline">github.com/CyberMind-FR/secubox-deb/issues</a></li>
|
||||
<li>📡 <b>Déployer une borne</b> près de chez toi : <a href="mailto:gondwana@cybermind.fr" style="color:var(--phos);text-decoration:underline">gondwana@cybermind.fr</a></li>
|
||||
<h2>💚 Soutenir le projet</h2>
|
||||
<p class="help">Commun numérique open-source, sans pub ni revente — CyberMind / Gérald Kerma (Savoie).</p>
|
||||
<ul style="margin-top:.4rem">
|
||||
<li>💰 <a href="https://liberapay.com/cybermind" style="color:var(--phos)">liberapay.com/cybermind</a></li>
|
||||
<li>🐛 <a href="https://github.com/CyberMind-FR/secubox-deb/issues" style="color:var(--phos)">Signaler un bug</a></li>
|
||||
</ul>
|
||||
<p style="font-size:0.78rem;color:var(--dim);margin-top:0.7rem;border-top:1px solid var(--dim);padding-top:0.5rem">
|
||||
Audit SecuBox premium / déploiement collectivité / formation cybersécurité :
|
||||
<a href="mailto:contact@cybermind.fr" style="color:var(--phos);text-decoration:underline">contact@cybermind.fr</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Gondwana ToolBoX · LicenseRef-CMSD-1.0 (Source-Disclosed License)<br>
|
||||
Source : <a href="https://github.com/CyberMind-FR/secubox-deb" style="color:var(--dim)">github.com/CyberMind-FR/secubox-deb</a> (#474 #475 #477)<br>
|
||||
CyberMind — Notre-Dame-du-Cruet (73130) · <a href="https://cybermind.fr" style="color:var(--dim)">cybermind.fr</a>
|
||||
Gondwana ToolBoX · LicenseRef-CMSD-1.0 · ↻ rafraîchit toutes les 20 s<br>
|
||||
<a href="https://github.com/CyberMind-FR/secubox-deb" style="color:var(--dim)">github.com/CyberMind-FR/secubox-deb</a> · <a href="https://cybermind.fr" style="color:var(--dim)">cybermind.fr</a>
|
||||
</div>
|
||||
|
||||
</body></html>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,53 @@
|
|||
secubox-toolbox (2.7.11-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat: landing (kbin.gk2) restyled to match the new report — system font,
|
||||
rounded --panel/--line cards, cleaner accents, softer SVG bars, helper lines,
|
||||
R3 panel mentions the 🧅 Tor egress option. Dynamic bits unchanged (live KPIs
|
||||
+ JS, per-OS install panels, cert-probe, ?mh links).
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 20:20:00 +0200
|
||||
|
||||
secubox-toolbox (2.7.10-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix: /report/me/html now resolves identity via the shared _client_mac_hash
|
||||
(?mh → R3 WG peer → captive ARP), so R3 clients reach their report without
|
||||
?mh (it previously only did captive ARP → 400 "client MAC unknown").
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 19:55:00 +0200
|
||||
|
||||
secubox-toolbox (2.7.9-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat: report (/report/me/html) regenerated — verdict-first + graphs + plain
|
||||
helpers. Top: a score gauge (conic-gradient) + 6 KPIs + 3 graphs (trackers
|
||||
donut, countries bars, apps bars) computed server-side (_build_report_charts).
|
||||
All the deep technical cards (threat-intel, DGA, beaconing, hosts, apps,
|
||||
cookies, avatar, transparency, per-host grades, identity, reco) collapse into
|
||||
<details> so the page reads instantly. System-font, rounded, mobile-first.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 19:30:00 +0200
|
||||
|
||||
secubox-toolbox (2.7.8-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix: landing "Ma carto" / "Mon rapport" / "Qui me piste ?" links hit
|
||||
/social/me + /report/me with NO ?mh=, so clicking them re-resolved identity
|
||||
at click-time and could 400 "client identity unresolved". The landing now
|
||||
resolves the caller's mac_hash (new _client_mac_hash helper: ?mh → R3 WG peer
|
||||
→ captive ARP) and bakes ?mh= into those links, so they always open the right
|
||||
client's view. No change to identity precedence; resolution stays server-side.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 18:40:00 +0200
|
||||
|
||||
secubox-toolbox (2.7.7-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix: injected banner trackers/cookies counts were stuck at 0. They were
|
||||
computed ONCE at render time — which fires early, before page resources +
|
||||
cookies have loaded (Resource Timing empty) — and the 2s poll's ensure()
|
||||
early-returned once the banner existed, so the counts never refreshed. Now
|
||||
the trackers/cookies spans have ids and updateCounts() re-counts live on the
|
||||
poll, so they climb to real values within ~2s.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Fri, 19 Jun 2026 18:10:00 +0200
|
||||
|
||||
secubox-toolbox (2.7.6-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(#683): the 🧅 Tor indicator now appears on the ACTUAL injected banner.
|
||||
|
|
|
|||
|
|
@ -328,6 +328,36 @@ def _client_ip(request: Request) -> str | None:
|
|||
return request.client.host if request.client else None
|
||||
|
||||
|
||||
def _client_mac_hash(request: Request, salt: str) -> str | None:
|
||||
"""Resolve the caller's identity hash, same precedence as /social/me:
|
||||
explicit ?mh= → R3 WG peer (wg-peers.json) → captive ARP. Returns None when
|
||||
unresolvable. Used to bake ?mh= into the landing links so they never hit the
|
||||
'identity unresolved' 400 (the page already knows who you are)."""
|
||||
mh_qp = (request.query_params.get("mh") or "").strip().lower()
|
||||
if mh_qp and all(c in "0123456789abcdef" for c in mh_qp) and 8 <= len(mh_qp) <= 64:
|
||||
return mh_qp
|
||||
ip = _client_ip(request)
|
||||
if ip and ip.startswith("10.99.1."):
|
||||
try:
|
||||
import hashlib as _h
|
||||
import json as _j
|
||||
from pathlib import Path as _P
|
||||
_db = _P("/var/lib/secubox/toolbox/wg-peers.json")
|
||||
if _db.exists():
|
||||
for pubkey, meta in _j.loads(_db.read_text()).get("peers", {}).items():
|
||||
if meta.get("ip") == ip:
|
||||
return _h.sha256(pubkey.encode()).hexdigest()[:16]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
_ip, mac = _resolve(request)
|
||||
if mac:
|
||||
return macmod.hash_mac(mac, salt)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
# ───────────────── Public routes ─────────────────
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
|
|
@ -701,11 +731,15 @@ async def landing(request: Request) -> HTMLResponse:
|
|||
stats = _cumulative_stats()
|
||||
platform = _ua_platform(request.headers.get("user-agent") or "")
|
||||
install_panels = _install_panels_html(platform)
|
||||
# Resolve identity now (the page knows who you are) so the report/carto links
|
||||
# carry ?mh= and never hit the /social/me "identity unresolved" 400.
|
||||
mac_hash = _client_mac_hash(request, _get_salt()) or ""
|
||||
return HTMLResponse(
|
||||
_env.get_template("landing.html.j2").render(
|
||||
stats=stats,
|
||||
install_panels=install_panels,
|
||||
install_platform=platform,
|
||||
mac_hash=mac_hash,
|
||||
),
|
||||
headers={"Cache-Control": "private, max-age=60, no-transform"},
|
||||
)
|
||||
|
|
@ -2308,6 +2342,46 @@ def _classify_apps(hosts: set[str]) -> list[str]:
|
|||
return apps
|
||||
|
||||
|
||||
def _build_report_charts(session: dict) -> dict:
|
||||
"""Graph-ready aggregates for the simplified report (trackers donut,
|
||||
countries bars, apps bars). Defensive / fail-empty. Each list item has
|
||||
{label, emoji/flag, count, pct}; trackers also carry cumulative start/end
|
||||
for a CSS conic-gradient donut."""
|
||||
def _top_pct(items: list, n: int = 6) -> list:
|
||||
items = [it for it in items if it.get("count")]
|
||||
items.sort(key=lambda x: x["count"], reverse=True)
|
||||
items = items[:n]
|
||||
total = sum(x["count"] for x in items) or 1
|
||||
for it in items:
|
||||
it["pct"] = round(100 * it["count"] / total)
|
||||
return items
|
||||
|
||||
cp = session.get("cookies_providers") or []
|
||||
trackers = _top_pct([
|
||||
{"label": p.get("provider", "?"), "emoji": p.get("emoji", "🍪"),
|
||||
"count": int(p.get("count", 0) or 0)} for p in cp])
|
||||
cum = 0
|
||||
for it in trackers:
|
||||
it["start"] = cum
|
||||
cum += it["pct"]
|
||||
it["end"] = cum
|
||||
|
||||
by_country: dict = {}
|
||||
for h in (session.get("geo_top_hosts") or []):
|
||||
key = (h.get("flag") or "🏴", h.get("country") or "?")
|
||||
by_country[key] = by_country.get(key, 0) + int(h.get("count", 0) or 0)
|
||||
countries = _top_pct([
|
||||
{"flag": k[0], "label": k[1], "count": v} for k, v in by_country.items()])
|
||||
|
||||
dc = session.get("dpi_classified") or {}
|
||||
apps = _top_pct([
|
||||
{"label": a.get("app", "?"), "emoji": a.get("emoji", "📦"),
|
||||
"count": int(a.get("count", 0) or 0)}
|
||||
for a in (dc.get("top_apps") or []) if a.get("app") not in (None, "", "?")])
|
||||
|
||||
return {"trackers": trackers, "countries": countries, "apps": apps}
|
||||
|
||||
|
||||
# NOTE: route order matters in FastAPI — specific routes (/report/me,
|
||||
# /report/me/html) MUST be declared BEFORE the catch-all /report/{token},
|
||||
# otherwise FastAPI matches /report/me with token="me" and returns 404.
|
||||
|
|
@ -2323,17 +2397,16 @@ async def report_me_html(request: Request) -> HTMLResponse:
|
|||
their own report. The hash for R3 = sha256(wg_pubkey)[:16] derived
|
||||
by inject_banner.py and embedded in the banner 'Mon rapport' link.
|
||||
"""
|
||||
# Bypass path : explicit mac_hash in query (R3 WG or kbin remote viewer)
|
||||
mh_qp = (request.query_params.get("mh") or "").strip().lower()
|
||||
if mh_qp and all(c in "0123456789abcdef" for c in mh_qp) and 8 <= len(mh_qp) <= 64:
|
||||
ip = request.client.host if request.client else "?"
|
||||
mac_hash = mh_qp
|
||||
else:
|
||||
ip, mac = _resolve(request)
|
||||
if not mac:
|
||||
raise HTTPException(400, "client MAC unknown (not in captive subnet?) — use ?mh=<hash>")
|
||||
salt = _get_salt()
|
||||
mac_hash = macmod.hash_mac(mac, salt)
|
||||
# Resolve identity the same way everywhere: ?mh → R3 WG peer (wg-peers.json)
|
||||
# → captive ARP. R3 clients hitting this directly (no ?mh) now resolve too.
|
||||
mac_hash = _client_mac_hash(request, _get_salt())
|
||||
if not mac_hash:
|
||||
raise HTTPException(
|
||||
400,
|
||||
"client identity unresolved (not on R3 tunnel and not in captive "
|
||||
"subnet) — append ?mh=<hash> from your banner's report link",
|
||||
)
|
||||
ip = _client_ip(request) or (request.client.host if request.client else "?")
|
||||
session = _aggregate_session(mac_hash)
|
||||
# Phase 3 (#492) : pass query args + force no-cache so iPhone Safari
|
||||
# actually fetches the new template.
|
||||
|
|
@ -2347,6 +2420,7 @@ async def report_me_html(request: Request) -> HTMLResponse:
|
|||
current_level=store.get_client_level(mac_hash) if mac_hash else "r1",
|
||||
wg_enabled=wg_enabled,
|
||||
cumulative=cumulative,
|
||||
charts=_build_report_charts(session),
|
||||
**session,
|
||||
)
|
||||
return HTMLResponse(html, headers={
|
||||
|
|
|
|||
|
|
@ -151,12 +151,23 @@ _BANNER_CORE = r"""
|
|||
return Object.keys(seen).length;
|
||||
} catch (_) { return 0; }
|
||||
}
|
||||
function countCookies(){
|
||||
try { return document.cookie ? document.cookie.split(";").filter(function(x){return x.indexOf("=")>=0;}).length : 0; } catch (_) { return 0; }
|
||||
}
|
||||
// #683 — counts are taken at render time, but resources + cookies keep loading
|
||||
// AFTER the banner appears (early render → stuck at 0). Re-count live on the
|
||||
// 2s poll so trackers/cookies climb to their real values.
|
||||
function updateCounts(b){
|
||||
var t = document.getElementById("sbx-trk");
|
||||
if (t) t.textContent = "🛰️ " + countTrackers((b || {}).tracker_patterns) + " trackers";
|
||||
var c = document.getElementById("sbx-ck");
|
||||
if (c) c.textContent = "🍪 " + countCookies() + " cookies";
|
||||
}
|
||||
function render(b){
|
||||
if (dismissed) return;
|
||||
if (document.getElementById("sbx-banner")) return;
|
||||
var trk = countTrackers(b.tracker_patterns);
|
||||
var ck = 0;
|
||||
try { ck = document.cookie ? document.cookie.split(";").filter(function(x){return x.indexOf("=")>=0;}).length : 0; } catch (_) {}
|
||||
var ck = countCookies();
|
||||
var bar = document.createElement("div");
|
||||
bar.id = "sbx-banner";
|
||||
bar.setAttribute("style", "position:fixed;left:0;right:0;top:0;z-index:2147483647;"
|
||||
|
|
@ -174,8 +185,8 @@ _BANNER_CORE = r"""
|
|||
+ cspProof
|
||||
+ tor
|
||||
+ "<span>" + esc((b.level || "r1").toUpperCase()) + "</span>"
|
||||
+ "<span>🛰️ " + trk + " trackers</span>"
|
||||
+ "<span>🍪 " + ck + " cookies</span>"
|
||||
+ "<span id=\"sbx-trk\">🛰️ " + trk + " trackers</span>"
|
||||
+ "<span id=\"sbx-ck\">🍪 " + ck + " cookies</span>"
|
||||
+ pin
|
||||
+ "<a href=\"" + esc(b.report_url || "#") + "\" style=\"margin-left:auto;color:#2C70C0;text-decoration:none\">report ▸</a>"
|
||||
+ "<button aria-label=\"dismiss\" style=\"background:none;border:0;color:#8A9AA8;cursor:pointer;font-size:14px\">✕</button>";
|
||||
|
|
@ -186,7 +197,7 @@ _BANNER_CORE = r"""
|
|||
}
|
||||
// ensure(): (re)render the banner if it's absent and the bundle is loaded and
|
||||
// the user hasn't dismissed it. Cheap (a getElementById guard inside render).
|
||||
function ensure(){ if (bundle && !dismissed) ready(function(){ render(bundle); }); }
|
||||
function ensure(){ if (bundle && !dismissed) ready(function(){ if (document.getElementById("sbx-banner")) updateCounts(bundle); else render(bundle); }); }
|
||||
// SPA re-assert: wrap history nav + popstate (defer so the framework settles),
|
||||
// plus a light 2s poll as a catch-all for DOM re-renders that drop the banner.
|
||||
["pushState","replaceState"].forEach(function(m){
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user