- base.html: collapsible sidebar navigation, doc tooltip system, JS helpers (fmtNum, fmtPct, fmtDuration, ecGrid, buildTable, docHTML) - overview.html: SOC command center with stacked timeline, live alerts, campaigns panel, browser donut, 6 KPIs - detections.html: threat color dots, raw score column, click-to-navigate rows - network.html: JA4 rotation, brute-force, persistent threats tables, 6 KPIs - ip_detail.html: ASN/country KPIs, AE/XGB/campaign columns, enriched features - scores/traffic/features/models/classify: page_title blocks + doc tooltips - api.py: 9 new endpoints (campaigns, brute-force, ja4-rotation, recurrence, cascade, alerts, timeline-detail, ua-rotation) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
74 lines
4.3 KiB
HTML
74 lines
4.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}JA4 SOC — Classifier{% endblock %}
|
|
{% block page_title %}
|
|
Classification SOC
|
|
<span class="relative inline-block ml-1"><button onclick="docToggle(this)" class="doc-btn">?</button><div class="doc-panel">
|
|
<h4>Feedback analyste SOC</h4>
|
|
<p>Classifiez les IPs pour entraîner le modèle XGBoost supervisé. Les labels sont utilisés au prochain cycle ML.</p>
|
|
<p><strong>Bot :</strong> Confirme que l'IP est malveillante. <strong>Légitime :</strong> Faux positif. <strong>Suspect :</strong> À surveiller.</p>
|
|
<p class="doc-source">Source : soc_feedback → XGBoost training</p>
|
|
</div></span>
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="space-y-6">
|
|
<div class="bg-gray-900 rounded-xl p-6 border border-gray-800 space-y-4">
|
|
<div>
|
|
<label class="block text-sm text-gray-400 mb-1">Adresse IP</label>
|
|
<input type="text" id="cls-ip" placeholder="ex: 192.168.1.100" class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-300 focus:border-brand-500 focus:outline-none">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm text-gray-400 mb-1">Classification</label>
|
|
<select id="cls-type" class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-300">
|
|
<option value="bot">🤖 Bot malveillant</option>
|
|
<option value="legitimate">✅ Trafic légitime</option>
|
|
<option value="suspicious">⚠️ Suspect (à surveiller)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm text-gray-400 mb-1">Commentaire</label>
|
|
<textarea id="cls-comment" rows="3" placeholder="Raison de la classification..." class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-gray-300 focus:border-brand-500 focus:outline-none resize-none"></textarea>
|
|
</div>
|
|
<button id="cls-submit" class="px-6 py-2 bg-brand-600 text-white rounded-lg text-sm font-medium hover:bg-brand-700 transition-colors">Envoyer la classification</button>
|
|
<div id="cls-result" class="text-sm"></div>
|
|
</div>
|
|
<!-- Recent classifications -->
|
|
<div class="bg-gray-900 rounded-xl border border-gray-800 overflow-hidden">
|
|
<h3 class="text-sm font-medium text-gray-400 px-5 py-3 border-b border-gray-800">Classifications récentes</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="data-table"><thead><tr>
|
|
<th>Date</th><th>IP</th><th>Classification</th><th>Commentaire</th>
|
|
</tr></thead><tbody id="cls-history"></tbody></table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
{% block scripts %}
|
|
<script>
|
|
document.getElementById('cls-submit').onclick = async () => {
|
|
const ip = document.getElementById('cls-ip').value.trim();
|
|
if (!ip) { alert('Veuillez saisir une IP'); return; }
|
|
try {
|
|
const r = await fetch('/api/classify', {method:'POST', headers:{'Content-Type':'application/json'},
|
|
body:JSON.stringify({src_ip:ip, classification:document.getElementById('cls-type').value, comment:document.getElementById('cls-comment').value})});
|
|
const d = await r.json();
|
|
document.getElementById('cls-result').innerHTML = r.ok
|
|
? `<span class="text-green-400">✓ ${ip} classifié : ${d.classification}</span>`
|
|
: `<span class="text-red-400">✗ Erreur : ${d.detail||'unknown'}</span>`;
|
|
if (r.ok) loadHistory();
|
|
} catch(e) { document.getElementById('cls-result').innerHTML = `<span class="text-red-400">✗ ${e}</span>`; }
|
|
};
|
|
async function loadHistory() {
|
|
try {
|
|
const r = await fetch('/api/classifications'); const d = await r.json();
|
|
document.getElementById('cls-history').innerHTML = (d.data||[]).map(row => `<tr>
|
|
<td class="text-xs">${row.created_at||''}</td>
|
|
<td>${fmtIP(row.src_ip)}</td>
|
|
<td><span class="badge ${row.classification==='bot'?'badge-critical':row.classification==='legitimate'?'badge-low':'badge-medium'}">${row.classification}</span></td>
|
|
<td class="text-xs max-w-[300px] truncate">${row.comment||''}</td>
|
|
</tr>`).join('') || '<tr><td colspan="4" class="text-center text-gray-500 py-4">Aucune classification</td></tr>';
|
|
} catch(e) {}
|
|
}
|
|
loadHistory();
|
|
</script>
|
|
{% endblock %}
|