{% extends "base.html" %}
{% block title %}JA4 SOC — Analyse Réseau{% endblock %}
{% block page_title %}
    Analyse Réseau
    <span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
        <h4>Analyse réseau</h4>
        <p>Vue complète de l'infrastructure réseau : ASN, pays, fingerprints JA4, rotation de fingerprints, brute-force et menaces persistantes.</p>
        <p><strong>Workflow :</strong> Identifiez les ASN suspects → vérifiez la rotation JA4 → contrôlez le brute-force → investiguez les IPs récurrentes.</p>
        <p class="doc-source">Sources : view_ai_features_1h, view_host_ip_ja4_rotation, view_form_bruteforce_detected, view_ip_recurrence</p>
    </div></span>
{% endblock %}
{% block content %}
<div class="space-y-4">
    <!-- KPI Row -->
    <div class="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-6 gap-3">
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">Pays</div><div class="text-xl font-bold text-brand-500" id="kpi-countries">—</div></div>
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">ASNs</div><div class="text-xl font-bold text-yellow-400" id="kpi-asns">—</div></div>
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">Sessions ISP</div><div class="text-xl font-bold text-green-400" id="kpi-human">—</div></div>
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">Sessions DC</div><div class="text-xl font-bold text-red-400" id="kpi-datacenter">—</div></div>
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">Rotation JA4</div><div class="text-xl font-bold text-purple-400" id="kpi-rotation">—</div></div>
        <div class="kpi-card"><div class="text-[11px] text-gray-500 mb-1">Brute-force</div><div class="text-xl font-bold text-red-400" id="kpi-brute">—</div></div>
    </div>

    <!-- Row: ASN Treemap + Sunburst -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <div class="section-card">
            <div class="section-header"><span class="section-title">Treemap ASN
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Treemap ASN</h4>
                    <p>Taille = nombre de sessions. Regroupé par type : <span class="text-green-400">ISP</span> (résidentiel), <span class="text-red-400">Datacenter</span>, <span class="text-orange-400">Hosting</span>, <span class="text-cyan-400">CDN</span>.</p>
                    <p><strong>Action :</strong> Un ASN datacenter avec beaucoup de sessions mérite investigation.</p>
                    <p class="doc-source">Source : view_ai_features_1h</p>
                </div></span>
            </span></div>
            <div class="section-body"><div id="chart-treemap" style="height:340px"></div></div>
        </div>
        <div class="section-card">
            <div class="section-header"><span class="section-title">Pays → Type ASN
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Sunburst géographique</h4>
                    <p>Niveau 1 : pays. Niveau 2 : type d'ASN. Identifiez les pays avec forte proportion datacenter.</p>
                    <p class="doc-source">Source : view_ai_features_1h</p>
                </div></span>
            </span></div>
            <div class="section-body"><div id="chart-sunburst" style="height:340px"></div></div>
        </div>
    </div>

    <!-- Row: JA4 Rotation + Brute-force -->
    <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <!-- JA4 Rotation -->
        <div class="section-card">
            <div class="section-header"><span class="section-title">
                <svg class="w-4 h-4 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/></svg>
                Rotation JA4 (évasion TLS)
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Rotation de fingerprints JA4</h4>
                    <p>IPs utilisant plusieurs fingerprints TLS distinctes sur une fenêtre horaire. Indique une tentative d'évasion de détection (rotation de client TLS).</p>
                    <p><strong>Seuil critique :</strong> ≥ 3 JA4 distincts par IP/host/heure.</p>
                    <p class="doc-source">Source : view_host_ip_ja4_rotation</p>
                </div></span>
            </span></div>
            <div class="section-body p-0">
                <div class="overflow-x-auto" style="max-height:300px; overflow-y:auto">
                    <table class="data-table"><thead><tr>
                        <th>IP</th><th>Host</th><th>JA4 distincts</th><th>Hits</th><th>Fenêtre</th>
                    </tr></thead><tbody id="rotation-body"></tbody></table>
                </div>
            </div>
        </div>
        <!-- Brute-force -->
        <div class="section-card">
            <div class="section-header"><span class="section-title">
                <svg class="w-4 h-4 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
                Brute-force / Credential stuffing
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Détection brute-force</h4>
                    <p>IPs envoyant ≥10 requêtes POST par host sur 24h. Indique du credential stuffing ou du brute-force de formulaires.</p>
                    <p class="doc-source">Source : view_form_bruteforce_detected</p>
                </div></span>
            </span></div>
            <div class="section-body p-0">
                <div class="overflow-x-auto" style="max-height:300px; overflow-y:auto">
                    <table class="data-table"><thead><tr>
                        <th>IP</th><th>Host</th><th>POSTs</th><th>Paths</th><th>Première</th><th>Dernière</th>
                    </tr></thead><tbody id="brute-body"></tbody></table>
                </div>
            </div>
        </div>
    </div>

    <!-- Row: Persistent threats + JA4 table -->
    <div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
        <!-- Persistent threats -->
        <div class="section-card">
            <div class="section-header"><span class="section-title">
                <svg class="w-4 h-4 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
                Menaces persistantes
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>IPs récurrentes</h4>
                    <p>IPs détectées sur plusieurs fenêtres horaires. Récurrence élevée = acteur persistant. Le score indique le pire score observé.</p>
                    <p class="doc-source">Source : view_ip_recurrence</p>
                </div></span>
            </span></div>
            <div class="section-body p-0">
                <div class="overflow-y-auto" style="max-height:360px">
                    <table class="data-table"><thead><tr>
                        <th>IP</th><th>Réc.</th><th>Score</th><th>Threat</th>
                    </tr></thead><tbody id="recurrence-body"></tbody></table>
                </div>
            </div>
        </div>
        <!-- JA4 Fingerprints -->
        <div class="lg:col-span-2 section-card">
            <div class="section-header"><span class="section-title">Empreintes JA4
                <span class="relative inline-block"><button onclick="docToggle(this)" class="doc-btn">ⓘ</button><div class="doc-panel">
                    <h4>Fingerprints TLS (JA4)</h4>
                    <p>Chaque combinaison unique de paramètres TLS génère un hash JA4. Les navigateurs courants partagent des fingerprints connues.</p>
                    <p><strong>Indicateurs :</strong> Velocity élevée + browser score bas = bot probable.</p>
                    <p class="doc-source">Source : view_ai_features_1h GROUP BY ja4</p>
                </div></span>
            </span></div>
            <div class="section-body p-0">
                <div class="overflow-x-auto" style="max-height:360px; overflow-y:auto">
                    <table class="data-table" id="ja4-table"><thead><tr>
                        <th class="cursor-pointer" data-col="0">JA4</th>
                        <th class="cursor-pointer" data-col="1">Sessions</th>
                        <th class="cursor-pointer" data-col="2">Hits</th>
                        <th class="cursor-pointer" data-col="3">Velocity</th>
                        <th class="cursor-pointer" data-col="4">Fuzz</th>
                        <th class="cursor-pointer" data-col="5">Browser</th>
                        <th>Label</th><th>Bot</th>
                    </tr></thead><tbody id="ja4-body"></tbody></table>
                </div>
            </div>
        </div>
    </div>

    <!-- Row: ASN table + Bot pie -->
    <div class="grid grid-cols-1 lg:grid-cols-3 gap-4">
        <div class="lg:col-span-2 section-card">
            <div class="section-header"><span class="section-title">Détail ASN</span></div>
            <div class="section-body p-0">
                <div class="overflow-x-auto" style="max-height:360px; overflow-y:auto">
                    <table class="data-table"><thead><tr>
                        <th>ASN Org</th><th>Label</th><th>Pays</th><th>Sessions</th><th>Hits</th><th>Velocity</th><th>Fuzz</th>
                    </tr></thead><tbody id="asn-body"></tbody></table>
                </div>
            </div>
        </div>
        <div class="section-card">
            <div class="section-header"><span class="section-title">Bots par empreinte</span></div>
            <div class="section-body"><div id="chart-botpie" style="height:300px"></div></div>
        </div>
    </div>
</div>
{% endblock %}
{% block scripts %}
<script>
const LABEL_COLORS = {isp:'#22c55e', datacenter:'#ef4444', hosting:'#f97316', cdn:'#06b6d4', unknown:'#6b7280'};

let charts = {};
function initChart(id) {
    const el = document.getElementById(id);
    if (!el) return null;
    if (charts[id]) charts[id].dispose();
    charts[id] = echarts.init(el);
    return charts[id];
}

let ja4Rows = [], sortCol = 1, sortAsc = false;
function renderJA4Table() {
    const sorted = [...ja4Rows].sort((a,b) => {
        const va = a[sortCol], vb = b[sortCol];
        if (typeof va === 'number') return sortAsc ? va-vb : vb-va;
        return sortAsc ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va));
    });
    document.getElementById('ja4-body').innerHTML = sorted.map(r =>
        `<tr onclick="window.location='/detections?ja4=${encodeURIComponent(r[0])}'">
            <td class="font-mono text-[11px]">${fmtJA4Full(r[0])}</td>
            <td>${r[1]}</td><td>${r[2]}</td><td>${r[3].toFixed(3)}</td><td>${r[4].toFixed(3)}</td><td>${r[5].toFixed(2)}</td>
            <td>${fmtLabel(r[6])}</td><td class="text-xs">${fmtBotName(r[7])}</td>
        </tr>`).join('');
}
document.getElementById('ja4-table').querySelector('thead').addEventListener('click', e => {
    const th = e.target.closest('th[data-col]'); if(!th) return;
    const col = parseInt(th.dataset.col);
    if(col===sortCol) sortAsc=!sortAsc; else { sortCol=col; sortAsc=false; }
    renderJA4Table();
});

async function loadAll() {
    try {
        const [geo, fp, rot, bf, rec] = await Promise.all([
            fetch('/api/geo').then(r=>r.json()),
            fetch('/api/fingerprints').then(r=>r.json()),
            fetch('/api/ja4-rotation').then(r=>r.json()),
            fetch('/api/brute-force').then(r=>r.json()),
            fetch('/api/recurrence').then(r=>r.json()),
        ]);

        const countries = geo.countries||[], asns = geo.asns||[];
        const ja4Stats = fp.ja4_stats||[], botJa4 = fp.bot_ja4||[];
        const rotData = rot.data||[], bfData = bf.data||[], recData = rec.data||[];

        // KPIs
        document.getElementById('kpi-countries').textContent = new Set(countries.map(c=>c.country_code)).size;
        document.getElementById('kpi-asns').textContent = new Set(asns.map(a=>a.asn_org)).size;
        document.getElementById('kpi-human').textContent = fmtNum(asns.filter(a=>a.asn_label==='isp').reduce((s,a)=>s+(a.sessions||0),0));
        document.getElementById('kpi-datacenter').textContent = fmtNum(asns.filter(a=>a.asn_label==='datacenter').reduce((s,a)=>s+(a.sessions||0),0));
        document.getElementById('kpi-rotation').textContent = rotData.length;
        document.getElementById('kpi-brute').textContent = bfData.length;

        // ASN Treemap
        const treemapChart = initChart('chart-treemap');
        if (treemapChart && asns.length) {
            const byLabel = {};
            asns.forEach(a => {
                const lbl = a.asn_label||'unknown';
                if(!byLabel[lbl]) byLabel[lbl] = {name:lbl, value:0, children:[], itemStyle:{color:LABEL_COLORS[lbl]||'#6b7280'}};
                byLabel[lbl].children.push({name:a.asn_org, value:a.sessions||0});
                byLabel[lbl].value += a.sessions||0;
            });
            treemapChart.setOption(ecBase({
                tooltip: ecTooltip({formatter:i=>`${i.name}<br>Sessions: <b>${(i.value||0).toLocaleString()}</b>`}),
                series:[{type:'treemap', data:Object.values(byLabel).sort((a,b)=>b.value-a.value),
                    width:'100%',height:'100%', label:{show:true,fontSize:11,color:'#fff'},
                    upperLabel:{show:true,height:20,fontSize:11,color:'#fff',fontWeight:'bold',backgroundColor:'transparent'},
                    itemStyle:{borderColor:'#111827',borderWidth:2,gapWidth:1},
                    levels:[{itemStyle:{borderColor:'#1f2937',borderWidth:3},upperLabel:{show:true}},{colorSaturation:[0.4,0.8]}],
                }]
            }));
            treemapChart.on('click', p => { if(p.data?.name && p.treePathInfo?.length>2) window.location.href='/detections?asn_org='+encodeURIComponent(p.data.name); });
        }

        // Sunburst
        const sunChart = initChart('chart-sunburst');
        if (sunChart && countries.length) {
            const byC = {};
            countries.forEach(c => {
                if(!byC[c.country_code]) byC[c.country_code] = {name:c.country_code, children:[]};
                byC[c.country_code].children.push({name:c.asn_label||'unknown', value:c.sessions||0, itemStyle:{color:LABEL_COLORS[c.asn_label]||'#6b7280'}});
            });
            sunChart.setOption(ecBase({
                tooltip:ecTooltip({formatter:i=>`${i.name}: ${(i.value||0).toLocaleString()}`}),
                series:[{type:'sunburst', data:Object.values(byC).sort((a,b)=>{const va=a.children.reduce((s,c)=>s+c.value,0);const vb=b.children.reduce((s,c)=>s+c.value,0);return vb-va;}),
                    radius:['15%','90%'], label:{color:'#e5e7eb',fontSize:10,rotate:'radial'},
                    itemStyle:{borderColor:'#111827',borderWidth:1},
                    levels:[{},{r0:'15%',r:'50%',label:{fontSize:12,fontWeight:'bold'}},{r0:'50%',r:'90%',label:{fontSize:9}}],
                }]
            }));
        }

        // JA4 Rotation table
        document.getElementById('rotation-body').innerHTML = rotData.map(r =>
            `<tr onclick="window.location='/ip/${encodeURIComponent(String(r.src_ip).replace('::ffff:',''))}'">
                <td>${fmtIP(r.src_ip)}</td>
                <td class="text-xs">${escapeHtml(r.host||'')}</td>
                <td class="text-center"><span class="badge badge-high">${r.distinct_ja4}</span></td>
                <td class="font-mono text-xs">${r.total_hits||0}</td>
                <td class="text-[11px] text-gray-400">${(r.window_start||'').substring(0,16)}</td>
            </tr>`).join('') || '<tr><td colspan="5" class="text-center py-4 text-gray-500">Aucune rotation détectée</td></tr>';

        // Brute-force table
        document.getElementById('brute-body').innerHTML = bfData.map(r =>
            `<tr onclick="window.location='/ip/${encodeURIComponent(String(r.src_ip).replace('::ffff:',''))}'">
                <td>${fmtIP(r.src_ip)}</td>
                <td class="text-xs">${escapeHtml(r.host||'')}</td>
                <td class="font-mono text-red-400">${r.post_count}</td>
                <td class="text-xs">${r.distinct_paths}</td>
                <td class="text-[11px] text-gray-400">${(r.first_seen||'').substring(0,16)}</td>
                <td class="text-[11px] text-gray-400">${(r.last_seen||'').substring(0,16)}</td>
            </tr>`).join('') || '<tr><td colspan="6" class="text-center py-4 text-gray-500">Aucun brute-force détecté</td></tr>';

        // Recurrence table
        document.getElementById('recurrence-body').innerHTML = recData.map(r =>
            `<tr onclick="window.location='/ip/${encodeURIComponent(String(r.src_ip).replace('::ffff:',''))}'">
                <td>${fmtIP(r.src_ip)}</td>
                <td class="text-center font-bold text-orange-400">${r.recurrence}</td>
                <td>${fmtScore(r.worst_score)}</td>
                <td>${threatBadge(r.worst_threat)}</td>
            </tr>`).join('') || '<tr><td colspan="4" class="text-center py-4 text-gray-500">Aucune récurrence</td></tr>';

        // JA4 table
        const botMap = {};
        botJa4.forEach(b => { botMap[b.ja4] = b.bot_name; });
        ja4Rows = ja4Stats.map(j => [j.ja4, j.sessions||0, j.total_hits||0, j.avg_velocity||0, j.avg_fuzz||0, j.avg_browser_score||0, j.asn_label||'unknown', botMap[j.ja4]||'']);
        renderJA4Table();

        // ASN table
        document.getElementById('asn-body').innerHTML = asns.sort((a,b)=>(b.sessions||0)-(a.sessions||0)).map(a =>
            `<tr><td class="text-xs">${fmtASN(a.asn_org)}</td><td>${fmtLabel(a.asn_label)}</td><td>${fmtCountry(a.country_code)}</td>
            <td>${(a.sessions||0).toLocaleString()}</td><td>${(a.total_hits||0).toLocaleString()}</td>
            <td>${(a.avg_velocity||0).toFixed(3)}</td><td>${(a.avg_fuzz||0).toFixed(3)}</td></tr>`).join('');

        // Bot pie
        const bpChart = initChart('chart-botpie');
        if (bpChart && botJa4.length) {
            const byBot = {};
            botJa4.forEach(b => { byBot[b.bot_name] = (byBot[b.bot_name]||0)+(b.sessions||0); });
            bpChart.setOption(ecBase({
                tooltip:ecTooltip({trigger:'item',formatter:'{b}: {c} ({d}%)'}),
                series:[{type:'pie',radius:['35%','75%'],center:['50%','55%'],
                    label:{color:EC_TEXT,fontSize:10,formatter:'{b}\n{d}%'},
                    data:Object.entries(byBot).map(([n,v],i)=>({name:n,value:v,itemStyle:{color:EC_COLORS[i%EC_COLORS.length]}})).sort((a,b)=>b.value-a.value),
                }]
            }));
        }
    } catch(e) { console.error('Network load error:', e); }
}
loadAll();
setInterval(loadAll, 60000);
</script>
{% endblock %}
