| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- {% extends "layout.html" %}
- {% block content %}
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
- <div class="flex justify-between items-end mb-8 border-b border-gray-800 pb-6">
- <div>
- <h2 class="text-4xl font-black text-white tracking-tighter italic">Live Analytics Command Center</h2>
- <p class="text-green-400 font-bold uppercase text-[10px] tracking-widest mt-2 flex items-center gap-2">
- <span class="flex h-2 w-2 relative"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span><span class="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span></span>
- Real-Time System Sync Enabled
- </p>
- </div>
- </div>
- <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl text-center relative overflow-hidden group hover:border-blue-500/30 transition-colors">
- <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Total Queries (Today)</h4>
- <span id="statTotalQueries" class="text-4xl font-black text-blue-400 font-mono relative z-10">0</span>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl text-center relative overflow-hidden group hover:border-green-500/30 transition-colors">
- <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Current RPS</h4>
- <span id="statRps" class="text-4xl font-black text-green-400 font-mono relative z-10">0.0</span>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl text-center relative overflow-hidden group hover:border-yellow-500/30 transition-colors">
- <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Peak RPS (Today)</h4>
- <span id="statPeakRps" class="text-4xl font-black text-yellow-400 font-mono relative z-10">0.0</span>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl text-center relative overflow-hidden group hover:border-purple-500/30 transition-colors">
- <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Unique Active IPs</h4>
- <span id="statUniqueIps" class="text-4xl font-black text-purple-400 font-mono relative z-10">0</span>
- </div>
- </div>
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
- <div class="flex justify-between items-center mb-4"><h3 class="text-[10px] font-black text-gray-500 uppercase tracking-widest">Requests Per Second</h3></div>
- <div class="h-40"><canvas id="rpsChart"></canvas></div>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
- <div class="flex justify-between items-center mb-4"><h3 class="text-[10px] font-black text-gray-500 uppercase tracking-widest">Top TLDs</h3></div>
- <div class="h-40 flex justify-center"><canvas id="tldChart"></canvas></div>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
- <div class="flex justify-between items-center mb-4"><h3 class="text-[10px] font-black text-gray-500 uppercase tracking-widest">Unique IPs Timeline</h3></div>
- <div class="h-40"><canvas id="ipsChart"></canvas></div>
- </div>
- </div>
- <div class="grid grid-cols-1 xl:grid-cols-2 gap-8 mb-10">
-
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl shadow-xl overflow-hidden flex flex-col h-[550px]">
- <div class="p-6 border-b border-gray-800 bg-gray-900/30">
- <h3 class="text-lg font-black text-white uppercase tracking-tighter italic">Top Client IPs</h3>
- </div>
- <div class="flex-1 overflow-y-auto custom-scroll" id="topIpsWrapper">
- <table class="w-full text-left whitespace-nowrap" data-sort-bound="false">
- <thead class="bg-black/40 text-[10px] font-black text-gray-500 uppercase tracking-widest sticky top-0 z-20 shadow-md">
- <tr><th class="p-4 pl-6 cursor-pointer hover:text-blue-400 group">Client IP <span class="sort-icon opacity-0 group-hover:opacity-50 text-blue-500">↕</span></th>
- <th class="p-4 text-right pr-6 cursor-pointer hover:text-blue-400 group">Total Queries <span class="sort-icon opacity-0 group-hover:opacity-50 text-blue-500">↕</span></th></tr>
- </thead>
- <tbody id="topIpsBody" class="divide-y divide-gray-800/50"></tbody>
- </table>
- </div>
- </div>
- <div class="bg-[#12161f] border border-gray-800 rounded-2xl shadow-xl flex flex-col h-[550px] relative">
- <div class="p-4 border-b border-gray-800 bg-gray-900/30 flex flex-col gap-4 z-10">
- <div class="flex justify-between items-center">
- <h3 class="text-lg font-black text-purple-400 uppercase tracking-tighter italic">Live Query Log</h3>
- <span id="activeIpLabel" class="text-[10px] font-black text-gray-500 bg-gray-800 px-3 py-1 rounded-full uppercase tracking-widest transition-all">Select an IP</span>
- </div>
- <div class="flex gap-2">
- <button id="btnPauseTerm" onclick="toggleTerminalPause()" class="bg-gray-800 hover:bg-gray-700 text-gray-300 text-[10px] font-black uppercase px-4 py-2 rounded-lg transition border border-gray-700 w-24">Pause</button>
- <input type="text" id="termSearch" oninput="updateTerminalFilter()" placeholder="Filter by domain or type (e.g. AAAA, google)..." class="flex-1 bg-black/50 border border-gray-700 rounded-lg px-4 text-xs text-white font-mono outline-none focus:border-purple-500 transition">
- </div>
- </div>
- <div class="bg-black flex-1 p-6 font-mono text-[11px] overflow-y-auto custom-scroll flex flex-col-reverse shadow-inner relative" id="liveTerminal">
- <div class="text-center text-gray-600 font-bold uppercase tracking-widest mt-20 flex flex-col items-center">
- <svg class="w-12 h-12 mb-4 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 9l3 3-3 3m5 0h3M4 17h16a2 2 0 002-2V9a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
- Click on an IP from the list<br>to view its live DNS traffic
- </div>
- </div>
- </div>
- </div>
- <script>
- let globalStatsInterval, activeIpInterval, currentTrackingIp = null;
- let rpsChartInst = null, ipsChartInst = null, tldChartInst = null;
- let isTerminalPaused = false;
- let terminalFilterText = '';
- function toggleTerminalPause() {
- isTerminalPaused = !isTerminalPaused;
- const btn = document.getElementById('btnPauseTerm');
- if(isTerminalPaused) {
- btn.innerText = "Resume";
- btn.classList.replace('bg-gray-800', 'bg-yellow-600/20');
- btn.classList.replace('text-gray-300', 'text-yellow-500');
- btn.classList.replace('border-gray-700', 'border-yellow-500/30');
- } else {
- btn.innerText = "Pause";
- btn.classList.replace('bg-yellow-600/20', 'bg-gray-800');
- btn.classList.replace('text-yellow-500', 'text-gray-300');
- btn.classList.replace('border-yellow-500/30', 'border-gray-700');
- }
- }
- function updateTerminalFilter() {
- terminalFilterText = document.getElementById('termSearch').value.toLowerCase();
- }
- function fetchGlobalStats() {
- fetch('/api/stats').then(r => r.json()).then(data => {
- const today = new Date().toISOString().split('T')[0];
- const todayData = data[today];
- if(!todayData) return;
- document.getElementById('statTotalQueries').innerText = todayData.queries.toLocaleString();
- document.getElementById('statUniqueIps').innerText = Object.keys(todayData.ips).length.toLocaleString();
-
- const times = Object.keys(todayData.timeline).sort();
- const rpsData = times.map(t => todayData.timeline[t].rps);
- const ipsData = times.map(t => todayData.timeline[t].unique_ips);
-
- const lastTime = times[times.length - 1];
- if(lastTime) document.getElementById('statRps').innerText = todayData.timeline[lastTime].rps;
-
- const peakRps = rpsData.length > 0 ? Math.max(...rpsData) : 0;
- document.getElementById('statPeakRps').innerText = peakRps;
- const tldLabels = Object.keys(todayData.tlds).sort((a,b) => todayData.tlds[b] - todayData.tlds[a]).slice(0, 5);
- const tldData = tldLabels.map(t => todayData.tlds[t]);
- const chartOptions = { animation: false, interaction: { mode: 'index', intersect: false }, responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { grid: { color: '#1f2937' }, beginAtZero: true } } };
- if(!rpsChartInst) {
- rpsChartInst = new Chart(document.getElementById('rpsChart'), { type: 'line', data: { labels: times, datasets: [{ label: 'RPS', data: rpsData, borderColor: '#3b82f6', fill: true, tension: 0.4 }] }, options: chartOptions });
- } else { rpsChartInst.data.labels = times; rpsChartInst.data.datasets[0].data = rpsData; rpsChartInst.update('none'); }
- if(!ipsChartInst) {
- ipsChartInst = new Chart(document.getElementById('ipsChart'), { type: 'bar', data: { labels: times, datasets: [{ label: 'Unique IPs', data: ipsData, backgroundColor: '#8b5cf6' }] }, options: chartOptions });
- } else { ipsChartInst.data.labels = times; ipsChartInst.data.datasets[0].data = ipsData; ipsChartInst.update('none'); }
- if(!tldChartInst) {
- tldChartInst = new Chart(document.getElementById('tldChart'), { type: 'doughnut', data: { labels: tldLabels, datasets: [{ data: tldData, backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#8b5cf6', '#6366f1'], borderWidth: 0 }] }, options: { animation: false, interaction: { mode: 'index', intersect: false }, responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#9ca3af', font: { size: 10 } } } }, cutout: '70%' } });
- } else { tldChartInst.data.labels = tldLabels; tldChartInst.data.datasets[0].data = tldData; tldChartInst.update('none'); }
- const ipEntries = Object.entries(todayData.ips).sort((a,b) => b[1] - a[1]);
- const tbody = document.getElementById('topIpsBody');
- tbody.innerHTML = '';
-
- ipEntries.slice(0, 100).forEach(([ip, count]) => {
- const tr = document.createElement('tr');
- const isActive = (ip === currentTrackingIp);
- tr.className = `group cursor-pointer transition-colors ${isActive ? 'bg-blue-600/10' : 'hover:bg-white/[0.02]'}`;
- tr.onclick = () => startTrackingIp(ip);
- tr.innerHTML = `
- <td class="p-4 pl-6 font-mono font-bold ${isActive ? 'text-blue-400' : 'text-gray-300 group-hover:text-blue-400'} flex items-center gap-2">
- ${isActive ? '<span class="w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse"></span>' : ''} ${ip}
- </td>
- <td class="p-4 pr-6 text-right font-mono text-gray-500">${count.toLocaleString()}</td>
- `;
- tbody.appendChild(tr);
- });
- }).catch(e => console.log('Analytics Sync Error:', e));
- }
- function startTrackingIp(ip) {
- currentTrackingIp = ip;
- document.getElementById('activeIpLabel').innerText = "Tracking: " + ip;
- document.getElementById('activeIpLabel').className = "text-[10px] font-black text-blue-400 bg-blue-900/30 px-3 py-1 rounded-full uppercase tracking-widest border border-blue-500/30 shadow-[0_0_10px_rgba(59,130,246,0.5)]";
- if(activeIpInterval) clearInterval(activeIpInterval);
- fetchIpLogs(ip);
- activeIpInterval = setInterval(() => fetchIpLogs(ip), 3000);
- fetchGlobalStats();
- }
- function fetchIpLogs(ip) {
- if(isTerminalPaused) return; // FIX: Αν είναι Paused, μην κάνεις update!
- fetch('/api/ip_queries/' + ip).then(r => r.json()).then(data => {
- const terminal = document.getElementById('liveTerminal');
-
- if(!data.queries || data.queries.length === 0) {
- terminal.innerHTML = '<div class="text-gray-600 italic mt-auto w-full text-center">No recent queries found for this IP...</div>';
- return;
- }
-
- // APPLY SEARCH FILTER
- let filteredQueries = data.queries;
- if(terminalFilterText !== "") {
- filteredQueries = filteredQueries.filter(q => q.domain.toLowerCase().includes(terminalFilterText) || q.type.toLowerCase().includes(terminalFilterText));
- }
- terminal.innerHTML = '';
- if(filteredQueries.length === 0) {
- terminal.innerHTML = '<div class="text-gray-600 italic mt-auto w-full text-center">No queries match your filter.</div>';
- return;
- }
- filteredQueries.forEach(q => {
- const div = document.createElement('div');
- div.className = 'mb-1 border-b border-gray-900/50 last:border-0 pb-1 flex gap-3 hover:bg-white/[0.05] p-1 rounded transition-colors';
- div.innerHTML = `
- <span class="text-gray-500 w-24 shrink-0">${q.time}</span>
- <span class="text-purple-400 w-10 shrink-0 font-black">${q.type}</span>
- <span class="text-green-400 flex-1 truncate" title="${q.domain}">${q.domain}</span>
- `;
- terminal.appendChild(div);
- });
- }).catch(e => console.log('IP Log Sync Error:', e));
- }
- document.addEventListener("DOMContentLoaded", () => {
- fetchGlobalStats();
- globalStatsInterval = setInterval(fetchGlobalStats, 10000);
- });
- </script>
- {% endblock %}
|