analytics.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. {% extends "layout.html" %}
  2. {% block content %}
  3. <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  4. <div class="flex justify-between items-end mb-8 border-b border-gray-800 pb-6">
  5. <div>
  6. <h2 class="text-4xl font-black text-white tracking-tighter italic">Live Analytics Command Center</h2>
  7. <p class="text-green-400 font-bold uppercase text-[10px] tracking-widest mt-2 flex items-center gap-2">
  8. <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>
  9. Real-Time System Sync Enabled
  10. </p>
  11. </div>
  12. </div>
  13. <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
  14. <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">
  15. <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Total Queries (Today)</h4>
  16. <span id="statTotalQueries" class="text-4xl font-black text-blue-400 font-mono relative z-10">0</span>
  17. </div>
  18. <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">
  19. <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Current RPS</h4>
  20. <span id="statRps" class="text-4xl font-black text-green-400 font-mono relative z-10">0.0</span>
  21. </div>
  22. <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">
  23. <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Peak RPS (Today)</h4>
  24. <span id="statPeakRps" class="text-4xl font-black text-yellow-400 font-mono relative z-10">0.0</span>
  25. </div>
  26. <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">
  27. <h4 class="text-[10px] font-black text-gray-500 uppercase tracking-widest mb-2 relative z-10">Unique Active IPs</h4>
  28. <span id="statUniqueIps" class="text-4xl font-black text-purple-400 font-mono relative z-10">0</span>
  29. </div>
  30. </div>
  31. <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
  32. <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
  33. <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>
  34. <div class="h-40"><canvas id="rpsChart"></canvas></div>
  35. </div>
  36. <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
  37. <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>
  38. <div class="h-40 flex justify-center"><canvas id="tldChart"></canvas></div>
  39. </div>
  40. <div class="bg-[#12161f] border border-gray-800 rounded-2xl p-6 shadow-xl relative">
  41. <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>
  42. <div class="h-40"><canvas id="ipsChart"></canvas></div>
  43. </div>
  44. </div>
  45. <div class="grid grid-cols-1 xl:grid-cols-2 gap-8 mb-10">
  46. <div class="bg-[#12161f] border border-gray-800 rounded-2xl shadow-xl overflow-hidden flex flex-col h-[550px]">
  47. <div class="p-6 border-b border-gray-800 bg-gray-900/30">
  48. <h3 class="text-lg font-black text-white uppercase tracking-tighter italic">Top Client IPs</h3>
  49. </div>
  50. <div class="flex-1 overflow-y-auto custom-scroll" id="topIpsWrapper">
  51. <table class="w-full text-left whitespace-nowrap" data-sort-bound="false">
  52. <thead class="bg-black/40 text-[10px] font-black text-gray-500 uppercase tracking-widest sticky top-0 z-20 shadow-md">
  53. <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>
  54. <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>
  55. </thead>
  56. <tbody id="topIpsBody" class="divide-y divide-gray-800/50"></tbody>
  57. </table>
  58. </div>
  59. </div>
  60. <div class="bg-[#12161f] border border-gray-800 rounded-2xl shadow-xl flex flex-col h-[550px] relative">
  61. <div class="p-4 border-b border-gray-800 bg-gray-900/30 flex flex-col gap-4 z-10">
  62. <div class="flex justify-between items-center">
  63. <h3 class="text-lg font-black text-purple-400 uppercase tracking-tighter italic">Live Query Log</h3>
  64. <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>
  65. </div>
  66. <div class="flex gap-2">
  67. <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>
  68. <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">
  69. </div>
  70. </div>
  71. <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">
  72. <div class="text-center text-gray-600 font-bold uppercase tracking-widest mt-20 flex flex-col items-center">
  73. <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>
  74. Click on an IP from the list<br>to view its live DNS traffic
  75. </div>
  76. </div>
  77. </div>
  78. </div>
  79. <script>
  80. let globalStatsInterval, activeIpInterval, currentTrackingIp = null;
  81. let rpsChartInst = null, ipsChartInst = null, tldChartInst = null;
  82. let isTerminalPaused = false;
  83. let terminalFilterText = '';
  84. function toggleTerminalPause() {
  85. isTerminalPaused = !isTerminalPaused;
  86. const btn = document.getElementById('btnPauseTerm');
  87. if(isTerminalPaused) {
  88. btn.innerText = "Resume";
  89. btn.classList.replace('bg-gray-800', 'bg-yellow-600/20');
  90. btn.classList.replace('text-gray-300', 'text-yellow-500');
  91. btn.classList.replace('border-gray-700', 'border-yellow-500/30');
  92. } else {
  93. btn.innerText = "Pause";
  94. btn.classList.replace('bg-yellow-600/20', 'bg-gray-800');
  95. btn.classList.replace('text-yellow-500', 'text-gray-300');
  96. btn.classList.replace('border-yellow-500/30', 'border-gray-700');
  97. }
  98. }
  99. function updateTerminalFilter() {
  100. terminalFilterText = document.getElementById('termSearch').value.toLowerCase();
  101. }
  102. function fetchGlobalStats() {
  103. fetch('/api/stats').then(r => r.json()).then(data => {
  104. const today = new Date().toISOString().split('T')[0];
  105. const todayData = data[today];
  106. if(!todayData) return;
  107. document.getElementById('statTotalQueries').innerText = todayData.queries.toLocaleString();
  108. document.getElementById('statUniqueIps').innerText = Object.keys(todayData.ips).length.toLocaleString();
  109. const times = Object.keys(todayData.timeline).sort();
  110. const rpsData = times.map(t => todayData.timeline[t].rps);
  111. const ipsData = times.map(t => todayData.timeline[t].unique_ips);
  112. const lastTime = times[times.length - 1];
  113. if(lastTime) document.getElementById('statRps').innerText = todayData.timeline[lastTime].rps;
  114. const peakRps = rpsData.length > 0 ? Math.max(...rpsData) : 0;
  115. document.getElementById('statPeakRps').innerText = peakRps;
  116. const tldLabels = Object.keys(todayData.tlds).sort((a,b) => todayData.tlds[b] - todayData.tlds[a]).slice(0, 5);
  117. const tldData = tldLabels.map(t => todayData.tlds[t]);
  118. 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 } } };
  119. if(!rpsChartInst) {
  120. 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 });
  121. } else { rpsChartInst.data.labels = times; rpsChartInst.data.datasets[0].data = rpsData; rpsChartInst.update('none'); }
  122. if(!ipsChartInst) {
  123. ipsChartInst = new Chart(document.getElementById('ipsChart'), { type: 'bar', data: { labels: times, datasets: [{ label: 'Unique IPs', data: ipsData, backgroundColor: '#8b5cf6' }] }, options: chartOptions });
  124. } else { ipsChartInst.data.labels = times; ipsChartInst.data.datasets[0].data = ipsData; ipsChartInst.update('none'); }
  125. if(!tldChartInst) {
  126. 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%' } });
  127. } else { tldChartInst.data.labels = tldLabels; tldChartInst.data.datasets[0].data = tldData; tldChartInst.update('none'); }
  128. const ipEntries = Object.entries(todayData.ips).sort((a,b) => b[1] - a[1]);
  129. const tbody = document.getElementById('topIpsBody');
  130. tbody.innerHTML = '';
  131. ipEntries.slice(0, 100).forEach(([ip, count]) => {
  132. const tr = document.createElement('tr');
  133. const isActive = (ip === currentTrackingIp);
  134. tr.className = `group cursor-pointer transition-colors ${isActive ? 'bg-blue-600/10' : 'hover:bg-white/[0.02]'}`;
  135. tr.onclick = () => startTrackingIp(ip);
  136. tr.innerHTML = `
  137. <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">
  138. ${isActive ? '<span class="w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse"></span>' : ''} ${ip}
  139. </td>
  140. <td class="p-4 pr-6 text-right font-mono text-gray-500">${count.toLocaleString()}</td>
  141. `;
  142. tbody.appendChild(tr);
  143. });
  144. }).catch(e => console.log('Analytics Sync Error:', e));
  145. }
  146. function startTrackingIp(ip) {
  147. currentTrackingIp = ip;
  148. document.getElementById('activeIpLabel').innerText = "Tracking: " + ip;
  149. 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)]";
  150. if(activeIpInterval) clearInterval(activeIpInterval);
  151. fetchIpLogs(ip);
  152. activeIpInterval = setInterval(() => fetchIpLogs(ip), 3000);
  153. fetchGlobalStats();
  154. }
  155. function fetchIpLogs(ip) {
  156. if(isTerminalPaused) return; // FIX: Αν είναι Paused, μην κάνεις update!
  157. fetch('/api/ip_queries/' + ip).then(r => r.json()).then(data => {
  158. const terminal = document.getElementById('liveTerminal');
  159. if(!data.queries || data.queries.length === 0) {
  160. terminal.innerHTML = '<div class="text-gray-600 italic mt-auto w-full text-center">No recent queries found for this IP...</div>';
  161. return;
  162. }
  163. // APPLY SEARCH FILTER
  164. let filteredQueries = data.queries;
  165. if(terminalFilterText !== "") {
  166. filteredQueries = filteredQueries.filter(q => q.domain.toLowerCase().includes(terminalFilterText) || q.type.toLowerCase().includes(terminalFilterText));
  167. }
  168. terminal.innerHTML = '';
  169. if(filteredQueries.length === 0) {
  170. terminal.innerHTML = '<div class="text-gray-600 italic mt-auto w-full text-center">No queries match your filter.</div>';
  171. return;
  172. }
  173. filteredQueries.forEach(q => {
  174. const div = document.createElement('div');
  175. 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';
  176. div.innerHTML = `
  177. <span class="text-gray-500 w-24 shrink-0">${q.time}</span>
  178. <span class="text-purple-400 w-10 shrink-0 font-black">${q.type}</span>
  179. <span class="text-green-400 flex-1 truncate" title="${q.domain}">${q.domain}</span>
  180. `;
  181. terminal.appendChild(div);
  182. });
  183. }).catch(e => console.log('IP Log Sync Error:', e));
  184. }
  185. document.addEventListener("DOMContentLoaded", () => {
  186. fetchGlobalStats();
  187. globalStatsInterval = setInterval(fetchGlobalStats, 10000);
  188. });
  189. </script>
  190. {% endblock %}