feat(dashboard): add clickable drill-down to all data elements
Add navigation helpers (fmtASN, fmtCountry, fmtJA4, fmtBotName,
fmtThreatLink, fmtLabel) to base.html for SOC analyst drill-down.
Update all templates:
- overview.html: clickable table cells + ECharts click handlers for
ASN, country, JA4, bot, and threat charts
- detections.html: URL param pre-filters, active filter bar with
clear buttons, clickable ASN/country/JA4/threat in table
- scores.html: URL param pre-filters, clickable threat/JA4/country
- traffic.html: clickable JA4 and country columns
- ip_detail.html: clickable threat/JA4 in detections, clickable
asn_org/country_code/asn_label in AI features grid
- network.html: click handlers on ASN treemap and country sunburst,
fmtJA4Full/fmtLabel/fmtBotName/fmtASN in tables
- features.html: scatter plot click navigates to /ip/{ip}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@ -4,9 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}JA4 SOC Dashboard{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
@ -14,7 +16,10 @@
|
||||
extend: {
|
||||
colors: {
|
||||
brand: { 50:'#eef2ff',100:'#e0e7ff',500:'#6366f1',600:'#4f46e5',700:'#4338ca',900:'#312e81' },
|
||||
}
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,16 +45,41 @@
|
||||
.badge-low { @apply bg-green-500/20 text-green-400; }
|
||||
.badge-normal { @apply bg-gray-500/20 text-gray-400; }
|
||||
.badge-known { @apply bg-blue-500/20 text-blue-400; }
|
||||
.htmx-request .htmx-indicator { display: inline-block; }
|
||||
.htmx-indicator { display: none; }
|
||||
.filter-btn { @apply px-3 py-1.5 text-xs rounded-lg border border-gray-700 text-gray-400 hover:border-brand-500 hover:text-brand-500 transition-colors cursor-pointer; }
|
||||
.filter-btn.active { @apply border-brand-500 bg-brand-500/20 text-brand-500; }
|
||||
|
||||
/* Micro-animations */
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.animate-in { animation: fadeUp 0.4s ease-out both; }
|
||||
.kpi-card { animation: fadeUp 0.4s ease-out both; }
|
||||
.kpi-card:nth-child(1) { animation-delay: 0ms; }
|
||||
.kpi-card:nth-child(2) { animation-delay: 50ms; }
|
||||
.kpi-card:nth-child(3) { animation-delay: 100ms; }
|
||||
.kpi-card:nth-child(4) { animation-delay: 150ms; }
|
||||
.kpi-card:nth-child(5) { animation-delay: 200ms; }
|
||||
.kpi-card:nth-child(6) { animation-delay: 250ms; }
|
||||
|
||||
/* Live status pulse */
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
.live-dot {
|
||||
width: 6px; height: 6px;
|
||||
background: #22c55e;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-gray-950 text-gray-200 min-h-screen">
|
||||
<!-- Top Nav -->
|
||||
<nav class="bg-gray-900 border-b border-gray-800 sticky top-0 z-50">
|
||||
<nav class="sticky top-0 z-50 border-b border-gray-800" style="background: linear-gradient(135deg, #0f172a 0%, #111827 50%, #0f172a 100%);">
|
||||
<div class="max-w-[1600px] mx-auto px-4 flex items-center h-14 gap-2">
|
||||
<a href="/" class="flex items-center gap-2 mr-6">
|
||||
<div class="w-8 h-8 bg-brand-600 rounded-lg flex items-center justify-center text-white font-bold text-sm">J4</div>
|
||||
@ -60,10 +90,14 @@
|
||||
<a href="/scores" class="nav-link {% if active_page == 'scores' %}active{% endif %}">Scores</a>
|
||||
<a href="/traffic" class="nav-link {% if active_page == 'traffic' %}active{% endif %}">Trafic</a>
|
||||
<a href="/features" class="nav-link {% if active_page == 'features' %}active{% endif %}">Features</a>
|
||||
<a href="/network" class="nav-link {% if active_page == 'network' %}active{% endif %}">Réseau</a>
|
||||
<a href="/models" class="nav-link {% if active_page == 'models' %}active{% endif %}">Modèles</a>
|
||||
<a href="/classify" class="nav-link {% if active_page == 'classify' %}active{% endif %}">Classifier</a>
|
||||
<div class="flex-1"></div>
|
||||
<span class="text-xs text-gray-500" id="clock"></span>
|
||||
<span class="flex items-center gap-2 text-xs text-gray-500">
|
||||
<span class="live-dot"></span>
|
||||
<span id="clock"></span>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- Content -->
|
||||
@ -71,11 +105,13 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<script>
|
||||
// ── Clock ──
|
||||
function updateClock() {
|
||||
document.getElementById('clock').textContent = new Date().toLocaleString('fr-FR');
|
||||
}
|
||||
updateClock(); setInterval(updateClock, 1000);
|
||||
|
||||
// ── Existing helpers ──
|
||||
function threatBadge(level) {
|
||||
const map = {
|
||||
'CRITICAL':'badge-critical','HIGH':'badge-high','MEDIUM':'badge-medium',
|
||||
@ -94,6 +130,58 @@
|
||||
let color = n > 0.7 ? 'text-red-400' : n > 0.4 ? 'text-orange-400' : n > 0.1 ? 'text-yellow-400' : 'text-green-400';
|
||||
return `<span class="${color}">${n.toFixed(4)}</span>`;
|
||||
}
|
||||
|
||||
// ── Navigation helpers ──
|
||||
function fmtASN(org) {
|
||||
if (!org) return '';
|
||||
return `<a href="/detections?asn_org=${encodeURIComponent(org)}" class="text-blue-400 hover:underline cursor-pointer">${org}</a>`;
|
||||
}
|
||||
function fmtCountry(cc) {
|
||||
if (!cc) return '';
|
||||
const flags = {'FR':'🇫🇷','DE':'🇩🇪','NL':'🇳🇱','GB':'🇬🇧','ES':'🇪🇸','US':'🇺🇸','RU':'🇷🇺','IT':'🇮🇹','JP':'🇯🇵','CN':'🇨🇳','KR':'🇰🇷','BR':'🇧🇷','AU':'🇦🇺','CA':'🇨🇦','IN':'🇮🇳'};
|
||||
return `<a href="/detections?country_code=${encodeURIComponent(cc)}" class="hover:underline cursor-pointer">${flags[cc]||'🏳️'} ${cc}</a>`;
|
||||
}
|
||||
function fmtJA4(ja4) {
|
||||
if (!ja4) return '';
|
||||
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs" title="${ja4}">${ja4.substring(0,20)}…</a>`;
|
||||
}
|
||||
function fmtJA4Full(ja4) {
|
||||
if (!ja4) return '';
|
||||
return `<a href="/detections?ja4=${encodeURIComponent(ja4)}" class="text-purple-400 hover:underline cursor-pointer font-mono text-xs">${ja4}</a>`;
|
||||
}
|
||||
function fmtBotName(name) {
|
||||
if (!name) return '';
|
||||
return `<a href="/detections?bot_name=${encodeURIComponent(name)}" class="text-cyan-400 hover:underline cursor-pointer">${name}</a>`;
|
||||
}
|
||||
function fmtThreatLink(level) {
|
||||
if (!level) return '';
|
||||
return `<a href="/detections?threat_level=${encodeURIComponent(level)}" class="cursor-pointer">${threatBadge(level)}</a>`;
|
||||
}
|
||||
function fmtLabel(label) {
|
||||
if (!label) return '';
|
||||
const colors = {human:'text-green-400 bg-green-500/10',datacenter:'text-red-400 bg-red-500/10',hosting:'text-orange-400 bg-orange-500/10'};
|
||||
return `<span class="px-1.5 py-0.5 rounded text-xs ${colors[label]||'text-gray-400 bg-gray-500/10'}">${label}</span>`;
|
||||
}
|
||||
|
||||
// ── ECharts helpers ──
|
||||
const EC_COLORS = ['#6366f1','#22c55e','#f97316','#ef4444','#3b82f6','#eab308','#ec4899','#14b8a6','#8b5cf6','#f43f5e'];
|
||||
const EC_TEXT = '#9ca3af';
|
||||
const EC_GRID = '#374151';
|
||||
|
||||
function ecBase(overrides) {
|
||||
return Object.assign({
|
||||
backgroundColor: 'transparent',
|
||||
textStyle: { color: EC_TEXT, fontFamily: 'Inter, system-ui, sans-serif' },
|
||||
animation: true, animationDuration: 600,
|
||||
}, overrides);
|
||||
}
|
||||
|
||||
function ecTooltip(extra) {
|
||||
return Object.assign({
|
||||
backgroundColor: '#1f2937', borderColor: '#374151',
|
||||
textStyle: { color: '#e5e7eb', fontSize: 12 },
|
||||
}, extra);
|
||||
}
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user